Policy Not Prose
Never hard-code a decision's DATE or its AUTHOR into committed source, a comment, an XML doc comment, or documentation prose. State the rule; link to the policy record. The record carries the date and the name — once.
A rule written as "Maintainer, 2026-09-07: do it this way" embeds three different things in one sentence: the rule, when it took effect, and who decided. Only the first belongs in the place you are reading. The other two are data about the rule, and prose is the worst available store for them.
Why
Prose copies drift. The same decision gets restated in an AGENTS.md, two doc pages and a
skill. Change the decision and you must find all four; you will find three. The fourth goes on
instructing people for months, and it is indistinguishable from the live ones because they all look
equally authoritative.
Names go stale faster than rules. People change roles, hand over areas, and leave. The rule outlives the attribution, so a name in a comment converts a durable instruction into something that reads as gossip about a decision nobody can now ask about. It also invites the wrong question — "is this still true, or just what someone said once?"
Prose cannot be queried. "Which policies are in force, and since when?" is a reasonable thing to ask a system. If the answers live in sentences scattered across repos, there is no answer — only a grep and a guess. A register answers it in one read.
The shape
A policy is a record with five fields and a link:
| field | what it is |
|---|---|
| id | a stable slug — what other pages cite |
| value | the decision itself, in as few words as carry it |
| status | in force, or proposed when it was cited before it existed — the field the self-healing step below turns on |
| in force since | the date it started applying |
| set by | the role or identity that set it — a role where one exists |
Everything else — source, comments, AGENTS.md, docs, skills — states the rule and cites the id. No dates, no names.
- # A What's New entry is written per RELEASE, not per change (maintainer, 2026-09-20)
+ # A What's New entry is written per RELEASE, not per change — policy `whatsnew-cadence`
🚨 What this does NOT forbid
This rule is about policy markers and attributions, not about dates as such. A date that is evidence stays exactly where it is:
- a measurement — "the folder reached 1,292 entries, 662 in August"
- an incident — "on 2026-09-19 the observation window lapsed and nothing wrote a terminal state"
- a historical record — a migration note, a retired mechanism, a changelog entry
Those are facts about events, and an event without its date is not a fact. The test is simple: would this date need changing if the decision changed? If yes, it is a policy marker and belongs in the register. If no, it is evidence and belongs where it is.
For review
A diff that introduces a hard-coded policy date or a person's name into source, a comment, an XML doc comment or doc prose is a review finding. The fix is never to delete the information — it is to move it: add or cite a register entry, and leave a link behind.
The finding is self-healing — it never blocks
A reviewer who finds a hard-coded date or name does not hand back a chore. They run a two-step that always terminates:
- Look for a policy that already covers it in the register below. Found → replace the prose with the citation. Done, no issue, one line changed.
- Not found → file an issue to introduce the policy, add a
proposedrow to the register naming that issue, and cite it in place.
The citation is written first and the policy catches up. That is the whole trick: the text is never blocked on the policy existing, and the reviewer is never asked to settle a policy question in a review thread — which is the thing that turns a two-line finding into a three-day argument. Because step 2 adds the register row immediately, a citation never dangles; what is pending is the ratification, not the reference.
This is why the rule converges without a migration. Nobody sweeps the corpus, but every page that gets edited for its own reasons leaves behind one more citation and, where it was missing, one more policy. The register fills from actual traffic — the decisions people actually touch — rather than from an archaeology project, and the pages nobody edits are, by definition, the pages nobody is misled by.
File the issue and carry on. Do not stop the change you came to make in order to ratify a policy you just discovered was implicit — that is the incidental-findings rule, and it applies here exactly as it does everywhere else.
This is the hamster wheel, applied to policy. Triage → implement → test, with every step filing what it finds back into triage rather than absorbing it: the same loop, and the same reason. A change that stopped to ratify every implicit policy it brushed against would land late, review badly, and abandon what it set out to do. The exit has to be cheap or the wheel stops turning — so the exit here is one register row and one issue, and then you carry on with what you were doing.
🚨 Forward-only. No backward migration.
This rule applies to what is WRITTEN FROM NOW ON. It is not a licence to sweep the existing corpus, and a pull request whose purpose is to retrofit old pages is out of scope.
The existing prose is not a defect. It records decisions that were taken and communicated the way the house wrote at the time, it is accurate, and rewriting it would touch a large number of pages to change nothing a reader relies on — while burning the review attention that new work needs. A migration would also be the more dangerous edit, because mechanical rewriting of attributions is exactly the kind of change that quietly alters meaning in the one page nobody re-reads.
So a reviewer checks the direction of travel: does this diff ADD a hard-coded policy date or name? If it does, that is the finding. If it merely fails to remove existing ones, that is not. Old pages migrate only when they are being edited anyway for their own reasons, and only the lines already being touched.
The register
A row is in force once ratified, or proposed when a reviewer cited it before it existed —
a proposed row names the issue that will settle it, so the citation resolves from the moment it is
written.
| id | value | status | in force since | set by |
|---|---|---|---|---|
platform-rolls-independently-of-packages |
The platform rolls independently of its packages: a package-availability verdict is NEVER a hold on the platform roll. A missing content bake (also under Modules:RequirePrebuilt), an unresolvable framework identity, a package without an artifact, an indeterminate or unreadable catalogue or plugin list, an unloadable module, a declared break, a recorded combo Red and an unwired gate are each an ADVISORY — logged, recorded on Admin/UpdatePolicy (Advisories, never HeldTag), carried on the check verdict and shown on the Updates and About tabs — and every roll path (the self-update poller, CD's roll target, the manual roll, the reboot image step) takes the newest admitted tag. Packages catch up through their own update policy (packages-auto-update) or the held-update lane. The pure package predicate (ReleaseAvailability.IsUpdatable) keeps answering the package question truthfully; the waiver is UpdatabilityVerdict.ForPlatformRoll. Manual: Release Strategy → "The platform rolls independently of its packages". |
in force | 2026-10-11 | maintainer |
portal-next-in-platform-image |
The Next.js /next front end rides the platform release train: it ships INSIDE memex-portal-ai (/app/portal-next.zip, built by main-cd's portal-ai leg from the same MeshWeaver.Plugins commit as the portal host and asserted in the published bytes of every architecture before promote), and the portal process serves it — so it carries the portal's version, 3-latest and every roll, and is never a separate image. A deployment states portalNext.enabled and no image; portalNext.image is only the chart's developer override. Maintainer: "yes, please bring next onto same release train. we can bundle in same package." Manual: Release Process → /next rides the same train. |
in force | 2026-10-11 | maintainer |
deployment-config-authorizes-named-commercial |
A COMMERCIAL package (a price or a sales contact) that a deployment names EXACTLY in PluginCatalog:InstallByDefault (Source/Package, never a whole-source Source/*) is INSTALLED by the unattended default install, together with what it requires (commercial requirements included), instead of being recorded under skipped ("can you update deployment config so that first install in client-a automatically has these modules" — "it should not skip them for crying out loud"). The authorizing principal is the deployment configuration itself, deployment-config:{PluginCatalog:InstanceId}: the deployment record changes only by a reviewed, App-merged pull request. Core's entitlement gate verifies the principal against the configuration the process booted with (never its string, never another instance), and it is logged, stamped on the install record (authorizedBy) and recorded on the default-install ledger (authorized). A whole-source pattern names nothing: it still holds newly listed packages and never installs or pulls in a commercial one. The ledger still seeds once, a plan-tier refusal and a licence requiring acceptance still keep a package out, and installing grants nobody access — the plan and the subscriptions decide who reads it. Manual: Plugin Registry. |
in force | 2026-10-11 | maintainer |
default-install-modules-first |
The unattended default install lands the compiled MODULE of every selected, not-yet-installed package BEFORE any package's nodes are written, and only then installs the nodes in dependency order ("Fix the platform bootstrap"). A module's bytes depend on no node, so this is what lets an image that ships none of the store-delivered modules install the store at all (Store's nodes need Skill from the AI module; AI's root needs Store/Plugin from Store). The same gates the install funnel applies before a file travels apply first (entitlement, licence acceptance, declared parameters, the adoption's platform-floor hold); an already-installed package keeps its module on the funnel; the "NodeType must be registered" validation is unchanged — a node install that fails before the landed module loads is retried by the pass after the restart. Manual: Plugin Registry. |
in force | 2026-10-14 | maintainer |
declared-package-set-is-exact |
An instance whose PluginCatalog:InstallByDefault names an EXPLICIT set — every configured entry a well-formed Source/Package, none a whole source Source/* — installs exactly those packages and what they declare in requires; the registry's preInstalled baseline is not added on top unless PluginCatalog:InstallPreInstalledPackages is set true (an explicit false suppresses it everywhere). No patterns, a whole-source pattern, or a malformed entry keeps the baseline ("only the declared packages (plus their declared requires) install when the instance declares a set"). Manual: Plugin Registry. |
in force | 2026-10-14 | maintainer |
package-set-one-signature-round |
Installing several packages on an instance takes ONE governed package.provision-set activity, reviewed and signed ONCE for the whole list ("pls don't give me hundreds of links to click"). It keeps the authority, gates and executing identity of package.provision: two signatures from people who are not the proposer, executed as System with governedBy. The signed packages input (plain ids separated by commas, semicolons or whitespace) authorizes exactly the packages it names. The Store installs exactly that set, in dependency order, and refuses the whole set before any write when one id is listed in no configured registry. Core's entitlement gate admits a commercial package only when the verified set names it, and the broad-grant allowlist carries the standard. It grants nobody access. Manual: Plugin Registry. |
in force | 2026-10-11 | maintainer |
chart-drift-compares-image-to-record |
Chart Drift compares the portal container's RUNNING image against its Deployment RECORD — never against the chart's image:, which is not what runs: the tag must equal pinnedImageTag when the record pins one, else match updatePattern; a digest-only reference, another repository, or a record stating neither is an OFF-RECORD finding, and a missing or ambiguous record is a FAILURE, never a skip. Only tag-level facts are printed. Manual: Chart Drift Semantics. |
in force | 2026-10-09 | maintainer |
one-values-half-per-release |
Each helm release has ONE Key Vault values-half secret (helm-values-<release>) holding EXACTLY the keys its Deployment record declares in vaultValuesKeys — what Provision writes, nothing a capture of the live release happens to carry. A half that feeds an undeclared key, or lacks a declared one, is refused before helm, by key NAME (never a value). Chart Drift renders with a placeholder half of exactly that shape. Manual: Chart Drift Render Without Secrets. |
in force | 2026-10-09 | maintainer |
image-rebuild-own-inputs |
A platform image is rebuilt only when one of ITS OWN inputs changed. Each repository classifies its own layout and both fail open: MeshWeaver.Plugins' portal-image-relevance.py (every host project's evaluated includes) dispatches main-cd with rebuild: true, and core's core-image-relevance.py skips a core commit whose every changed path is a test, agent configuration, repository-root prose or a standalone client. Movement in another repository never rebuilds an image by itself. CD Ledger → Rebuild only on the image's own inputs, #4688 |
in force | 2026-10-09 | maintainer |
bake-per-live-identity |
Plugin bundles are baked for every framework identity a live portal runs (option 1 of the identity-churn costing; options 2–4 are closed). Under the compatibility key, that is one identity per epoch, sealed by each node repository's own publish-bake lane (epoch-leg: current) on every push. Across a declared break the lane's epoch-leg: previous call ALSO bakes the NodeType content against the newest break's previousEpochCeiling, so a portal that has not rolled still gets a publication under its own key, until that break declares previousEpochRetired: true. Its MODULE bundles are never built a second time: the leg CARRIES FORWARD the repository's own sealed module set for the ceiling's identity, verified (sealed publication, mw-plugin-test module-identity --expect on every bundle, own-bundle sha256 among the carried ones), so a module fix does not reach a portal that has not rolled (Framework Identity Churn → carried forward). The live set is read from the declaration (platform-compatibility.json), never derived from a replica's report. Framework Identity Churn → The options, costed, #3583 |
in force | 2026-10-09 | maintainer |
module-live-update-default |
A module update goes live IN THE RUNNING PROCESS by default — each module in its own collectible load context; an update loads N+1, recycles the hubs bound to N and unloads N — with no flag, no opt-in, no governed activity and no approval. A restart is taken only when a module DECLARES restartRequired with a reason (a guard fails a module that blocks a live swap without the declaration) or when the live swap FAILS at runtime; then N keeps serving, the reason is recorded by name, and the automatic, approval-free self-update restart loads N+1. Modules update independently of the platform: platform contracts resolve from the running platform and are never bundled, admission is the declared floor against the running platform, and an image's module copy is only a boot baseline a newer generation supersedes live. Restart-required is legitimate ONLY for genuinely boot-time infrastructure — the declaration is [assembly: ModuleRestartRequired(ModuleBootCategory.<StorageDriver|Orleans|Authentication|Host>, "<why>")], a closed list; the guard (ModuleLiveUpdateGuard) fails any other category and any undeclared blocker. Owed: running the guard over every shipped module, the declarations, and the bundled-platform-assembly refusal in MeshWeaver.Plugins (slice 3). Manual: Live Module Update. |
in force | 2026-10-05 | maintainer |
cli-thin-wrapper-own-version |
The memex CLI (MeshWeaver.Cli, a dotnet tool) is a THIN client, like gh: the mesh verbs go over REST and build/test run inside the platform image, so it carries its OWN version (<CliVersion> in MeshWeaver.Cli.csproj, starting at 3.0.0) and is released rarely — when the CLI itself changes, never with every platform build. publish-packages.yml with packages=cli publishes exactly the declared version and refuses any other. Supersedes cli-on-nuget-follows-3-latest (2026-10-15, per-arming publication; withdrawn the next day before it ever published). It reverses the CLI's part of the 2026-09-07 NuGet retirement; the other retired packages stay retired. Manual: NuGet Package Retirement §1a. |
in force | 2026-10-16 | maintainer |
platform-semver-versioning |
Every platform build CD publishes from main is a plain SemVer release <major>.<minor>.<run>, a new version per build. The patch is the monotonic CD run number (handed in by name, -p:PlatformBuildNumber, by main-cd only), so the retired 3.0.0-ci.<run> notation and the new one share ONE lineage, and the cut-over can never roll backwards. The minor is bumped deliberately; the major only on a declared break (the same-major-is-compatible roll rule). No version carries a channel word (no ci, no rc); a build that publishes nothing is <major>.<minor>.0-dev, an edge build <major>.<minor>.0-edge.<run>. Images carry the floating tags <major>-latest and <major>.<minor>-latest, moved forward only by the arm step. A deployment record follows a version PATTERN resolved against the registry (3.*), never a floating tag. The new notation starts at line 3.1 (PlatformReleaseOrder.SemVerEraStart). Readers land first, then the records' patterns, then the minter. Manual: Platform Versioning (SemVer). |
in force | 2026-10-06 | maintainer |
tiered-pgo-off-5212 |
TEMPORARY STOPGAP for a .NET runtime defect, stated as one: every process that runs an in-process Roslyn Emit runs with DOTNET_TieredPGO=0 — the CI test, doc-gate and platform-compat jobs, the satellite lanes that compile or bake (node-repo-{compile-check,gate,module-pack,publish-bake}.yml, carried INTO each emitting docker run), and every portal through its Deployment record's extraPortalConfig (rendered by the chart's memex-portal-config ConfigMap). It complements the runtimeconfig half (<TieredPGO>false</TieredPGO>, #890), which reaches only hosts built from this tree. Every place it is set names #5212 and is removed when the runtime fix ships — with the split-arm measurement re-run on that runtime, never on faith. Root cause tracked in #5212 and upstream in dotnet/runtime. Manual: Node Type Compilation. |
in force | 2026-10-07 | maintainer |
stale-adoption-bound |
A NodeType build the source has moved past keeps serving (StaleAdopted, same module MAJOR) only while it is at most N MINOR versions behind the current source; past N it is refused like a MAJOR bump, named on the node and logged at Error, and within N every judgement and /health's live record census say how far behind it is. N is Modules:StaleAdoptionMaxMinorVersionsBehind, default 5 (the shipped default, ratified as the value); a deployment may override it, and a negative value disables the bound. Settled on Systemorph/Memex#668. Manual: Node Type Compilation. |
in force | 2026-10-07 | maintainer |
sole-maintainer-approval |
Every approval is four-eyes — the approver is not the requester — with ONE exception: the installation's DECLARED maintainer (Hosting:Operator:Maintainer, rendered from the deployment record's operator.maintainer; never a node an administrator can edit) may approve a request they filed themselves. ONE rule (SoleMaintainerApproval.Decide) decides it for every gate — instance actions, operation requests, and governed-activity signatures (a standard's own authority.maintainer). Every such approval is stamped who and when on the node, written to its log and shown on its page as "self-approved by maintainer"; everyone else still needs a different approver, and every other check of each gate is unchanged. ONE further, separately declared opt-in: where the installation also declares Hosting:Operator:MaintainerSignsAlone = true (the record's operator.maintainerSignsAlone, rendered only when true; never a node or a standard), that maintainer's ONE valid signature satisfies a governed activity's Signatures gate of ANY count — for a single-person installation with no confidential data. It changes nothing else: the signature must still be eligible, the maintainer's own write, bound, fresh and unconsumed; a standard naming a different authority.maintainer keeps its count; instance actions and operation requests have one approver and are untouched; and it is stamped (signedAloneBy), logged (SIGNED ALONE BY MAINTAINER) and shown ("Signed alone by maintainer") like a self-approval. Manual: Sole-Maintainer Approval. |
in force | 2026-10-10 | maintainer |
registry-trust-one-declaration |
Which portal validates a registry's instance keys, and which OCI host a registry's key may be presented to, is declared ONCE — on the registry's own hosting record — and every consumer reads that declaration; nothing is copied per consumer. (1) Self-update: the control instance DERIVES a consumer's selfUpdate.registryValidationUrl at render time from the hosting record whose registry.host equals the consumer's image host (MeshWeaver.Plugins HelmValues.DerivedRegistryValidationUrl); a hand-written SelfUpdate__RegistryValidationUrl extra keeps winning; no pairing or an ambiguous one renders nothing, so the consumer refuses as MeshWeaver#4094 pins. (2) Bundle client: the key held for a registry mount goes to an artifact's OCI host only when that host IS the mount's own host or the mount's bundle INDEX declares it (artifactRegistry, the registry's own registry.host) — never because a publisher-written bundle entry names it; an undeclared host is never contacted and the bytes come over the registry's HTTP route (PluginBundleClient.ArtifactKeyTarget). Manual: Self-Update Registry Credential. |
in force | 2026-10-09 | maintainer |
reset-database-keeps-a-copy |
An instance's database is reset to EMPTY only through the governed ResetDatabase Hosting/InstanceAction — never by a person's az postgres flexible-server db delete — and its approval follows sole-maintainer-approval: the declared maintainer, or a second global administrator. A reset ALWAYS renames and keeps the live database (<db>_kept_<yyyymmddhhmm>, stamped with when, until when and by which request), never drops it, and puts the old name back if the empty database cannot be created. The kept copy is retained 14 days; only then may the separate, equally approval-gated PurgeKeptDatabase action drop it, and it drops nothing that does not carry the kept-copy stamp. Mechanism: hosting-db-reset (operator), hosting-migrate --current --fresh (the empty database's migration), the plan in MeshWeaver.Plugins Hosting/InstanceAction. Decided on MeshWeaver.Plugins#2781. |
in force | 2026-10-09 | maintainer |
inbox-newest-first |
The personal inbox lists everything NEWEST FIRST, ALWAYS, as a mail client does — the order is not a choice (a stored order is ignored). What must be acted on stays visible through unread rows shown bold, and the noisiest source (the notification feed) starts switched OFF — one toggle away, the bell still showing it. The search narrows by date (before: / after:), so a range can be ticked and put away in one Delete. Manual: Communication Hub → Ordering. |
in force | 2026-10-13 | maintainer |
inbox-categories-by-kind |
The personal inbox files every item under a CATEGORY by kind — Approvals, Mail, Conversations, Notifications, Activities — listed in a side menu with counts, as a mail client's folders; a source names its category (InboxQueryLeg.Category, else its source). Picking a category shows every item filed under it, even from a source switched off by default. Manual: Communication Hub → Categories. |
in force | 2026-10-13 | maintainer |
inbox-connected-sources-only |
The personal inbox lists only the sources the viewer has CONNECTED: a source names how to tell (InboxQueryLeg.ConnectedWhen, anchored on the viewer's partition like the leg), and one that finds nothing gets no switch, no Only entry and no read; a connection that cannot be read lists its source and is logged. Manual: Communication Hub → Only connected sources. |
in force | 2026-10-15 | maintainer |
inbox-activities-need-someone |
Core's Activities source in the personal inbox lists only the runs MARKED for the inbox by the code that starts them (ActivityLog.ShowInInbox — a run a person asked for and waits on, or one that will need their decision) and still needing someone — going, or FAILED. Background runs (scripts, Tests-area runs, indexing) are never marked and never listed; a run that finished well is never listed either, its own page keeps it. Manual: Communication Hub → Bands. |
in force | 2026-10-13 | maintainer |
whatsnew-cadence |
A What's New entry is written per RELEASE, not per change. A merge updates its doc page and mints no dated file. | in force | 2026-09-20 | maintainer |
issue-taxonomy-scope |
Classification covers OPEN issues only. Closed issues are not classified, not counted, and appear in no query. | in force | 2026-09-20 | maintainer |
release-blocker-gate |
A release may not be cut while any sev:B or sev:H bug is open in the seven repositories that carry the taxonomy; sev:M and sev:L never gate a cut. Enforced by the release.cut standard in the Governance package. |
in force | 2026-09-20 | maintainer |
every-issue-has-a-severity |
Every OPEN issue carries exactly one sev: label, enhancements, documentation and chores included: a non-bug takes sev:M or sev:L, and the blocking severities sev:B/sev:H are reserved for defects — one on a non-bug is a violation. Measured daily by .github/workflows/issue-taxonomy.yml (scripts/check-issue-taxonomy.py). Manual: Issue Taxonomy. |
in force | 2026-10-15 | maintainer |
data-sync-approval |
Adding or widening the synchronisation of data needs a global admin's approval. | in force | 2026-09-20 | maintainer |
platform-backwards-compatibility |
Platform builds are backwards compatible within a major and a declared compatibility epoch — a ladder: a platform roll keeps the old plugin bytes (no rebuild, no re-seal) and plugins roll independently against the running platform. Compiled bytes are keyed on c<major>e<epoch>, never on a per-build identity (provenance only), and carry a platform range: floor (the producing build) ≤ running ≤ ceiling (open by default), else declined loudly naming both versions; platform assemblies bind whenever running ≥ compiled-against. Only a DECLARED break (an epoch or major bump in platform-compatibility.json, with a ceiling for the previous epoch) steps off the ladder and obliges a coordinated rebuild and seal; never seal or pin around a compatibility break — fix compatibility. Manual: Doc/Architecture/ModuleVersioning. |
in force | 2026-09-25 | maintainer |
version-shapes |
Exactly two version shapes: X.Y.Z-ci.<n> and clean X.Y.Z. No rc, preview or labelled line is ever minted. |
in force | 2026-09-07 | maintainer |
query-fanin-stall-terminal |
A query provider that neither emits, completes nor errors TERMINATES its merged query with a named error instead of hanging: the fan-in's Initial gate is a bound, not a wait. Every consumer that turns a mesh read into a decision reads that error as an availability failure — an access-control read fails CLOSED and reports that it could not be established, never denied and never granted. |
in force | 2026-09-21 | maintainer |
dynamic-content-type-registration-pass |
Every replica registers the content type of each already-baked dynamic NodeType it has not activated, in a PACED, REGISTRATION-ONLY pass after the boot's bake settles and OFF the readiness path: it loads the existing bytes and builds the configuration on a transient probe, and never compiles or writes a NodeType record. The cost is the ~13.5 s of assembly opening #1660 removed from boot, moved to that background pass. A read seam never takes the enrichment (compiling, record-writing) path. A read that meets a BUILT but unregistered type runs this same registration-only route on demand for that ONE type, coalesced per type, and is typed after it, so no reader waits for the pass (Plugins#2799). Mechanism: DynamicContentTypeRegistrar and ContentTypeOnDemandRegistration (MeshWeaver.Hosting); manual: Dynamic Content Type Registration. |
in force | 2026-09-25 (on-demand read: 2026-10-10) | maintainer |
open-vocabulary-string-constants |
A vocabulary that is persisted, serialised, or extended by a module is a static class of const string named exactly as the enum would have been — never a C# enum. It stays OPEN: any other party may add its own values from its own constants class, so the platform's set is never treated as exhaustive. Consumers compare against constants and always carry a branch for a value this build does not know. |
in force | 2026-09-21 | maintainer |
thread-graceful-error |
Wherever user code is executed, innermost in a thread, it must gracefully error: every failure path ends in a stamped terminal state on the node. A failure the thread's own hub cannot stamp — because it is the hub that died — is observed, cleaned up and relaunched under a bound, and what a relaunch cannot fix is filed into bug triage. The relaunch/dispatch side is bounded by a configurable pool cap (maxConcurrentAgents, default 50) whose queue is a page, not a log. |
in force | 2026-09-20 | maintainer |
roll-migrates-first |
A schema change ships as a new image, and every roll runs that image's database migration FIRST — outside the portal, as its own run-once Job that must report succeeded — before the image moves. The operator half is hosting-migrate; the Roll plan calling it is the Plugins half. |
in force | 2026-09-21 | maintainer |
db-migration-planned |
A database schema change is planned before it merges, and every step is enforced by code. Every migration is EXPAND-ONLY: the previous image must run correctly against the new schema, because old pods keep serving until new pods are fully ready (maxUnavailable: 0). A destructive change ships as a two-release expand→contract pair. A pull request that bumps DbVersion.Latest declares Db-migration: V<N> — <kind>; <compat>; rolls migrate-first and passes a rehearsal against a real Postgres (MeshWeaver.Plugins). Every release publishes its ExpectedDbVersion (_releases/_db/<version>, OCI expectedDbVersion). Every roll path runs the target's migration Job before the image moves, or refuses naming both numbers; the operator's run.sh interlock enforces this for every plan, whatever Hosting generation composed it. One documented exception remains until its lane is fixed: the break-glass HelmRelease dispatch (Systemorph/Memex helm-release.yml) applies the migration Job alongside the Deployment; use Reconcile instead. Manual: Doc/Architecture/PlanningADatabaseMigration. |
in force | 2026-09-25 | maintainer |
first-admin-no-learning-path |
The instance's FIRST global administrator is seeded with no learning path (User.PinnedPaths empty); an ordinary new user is seeded with the four documentation sections. The learning path is a create-time SEED, never a value a later onboarding write re-imposes. |
in force | 2026-09-21 | maintainer |
first-sign-in-becomes-admin |
A provisioned instance with no HUMAN administrator sends an anonymous visitor to SIGN-IN, and the first person to complete onboarding is admitted without an invitation and becomes platform admin; every later person needs one. "Human administrator" is ONE rule, asked by every gate (onboarding page load, onboarding submit, entry page): an undenied Admin grant in Admin/_Access whose holder is not System, Anonymous or Public and has an Active mesh User node. So a grant SEEDED for someone who has not onboarded (Auth:GlobalAdmins, the image's own settings) does not block the first person. It is read entirely as System and fails closed (unreadable ⇒ administered). The image SHOULD seed no administrator, but today it still does: Memex.Portal.Distributed/appsettings.json carries Auth:GlobalAdmins: ["rbuergi"], so every instance of the current image holds that static grant until MeshWeaver.Plugins#2431 lands. The rule above keeps it from blocking the first person, because the holder has no User node on a fresh instance. Safe ONLY where sign-in is restricted to the owning organisation; an instance whose sign-in admits the public is provisioned with an administrator. Mechanism: AdministratorProbeService.HasHumanAdministrator + OnboardingAdmissions (MeshWeaver.Plugins#2430), FirstPersonBecomesAdminOnAFreshInstanceTest; the image seed goes in MeshWeaver.Plugins#2431; account in First-run setup on a provisioned instance. |
in force | 2026-09-27 | maintainer |
explorer-navigation |
A node's children are navigated in a folder-like EXPLORER, never listed at the bottom of its page. Every node page carries the left-hand index — markdown pages, the typed DEFAULT Overview and Space pages (MeshWeaver.Plugins#3363) today; a node type that replaces its landing page through WithNodePage (a redirect, an enriched type's own page) is owed —: a lazy tree that loads only the levels on the reader's path (one direct-children query per level), at most 50 entries per level with a Load more… entry, drilled into by opening a folder, with a search box that opens the mesh search scoped to the space. No default layout appends a children catalog. Lists that span spaces (the home) are ONE flat list ordered by location (path) by default, never fanned out by node type; grouping by type is an admin's explicit choice. The platform's own partitions (WellKnownPartitions.Platform and Fleet) are never listed as a viewer's spaces: a space is a place people work together. Mechanism: DefaultNodeNavigation, MarkdownOverviewLayoutArea.WithIndexRail, NavMenuSkin.SearchNamespace, UserActivityLayoutAreas.BuildContentSection, RefreshSpacePathsLogonAction.SpacePathsFrom. Owed: the rail on pages a node type supplies itself through WithNodePage (decorated at the node-page composition boundary, with an opt-out for pages that must stay bare, such as a redirect); the platform-partition filter on the space list (#6470), and a level's render order (declared Order, nulls last, then name) applied by the store BEFORE its limit — today a level reads its first 501 rows by path and orders that window, so a row beyond the window in a larger level is reached by search only. Manual: The Supplied Navigation Rail, Apps Home. |
in force | 2026-10-11 | maintainer |
setup-secrets-three-valued |
The first-run setup dialog's secrets step shows a declared vault mapping whose object does not exist as its own state, missing, decided by the object's versions listing: 200 is exists, 404 is missing, anything else is not checked and never either answer. Missing rows sort first. |
in force | 2026-09-21 | maintainer |
platform-backwards-compatibility |
Platform builds within one major and one compatibility epoch are backwards compatible: plugin bytes built against platform N run UNCHANGED on platform N+1 — the ladder P1+p1 → P2+p1 → P2+p2 → P2+p3 → P3+p3, each step changing one side. Only a DECLARED epoch bump (src/MeshWeaver.Compiler/platform-compatibility.json, every broken member listed) breaks it; a plugin whose floor or producing platform is newer than the running one is declined loudly. Proven on every platform build, never assumed: the Platform compatibility … (ladder) check links the deployed plugin set against each platform pull request and hangs off the required check. |
in force | 2026-09-25 | maintainer |
cluster-upgrade-governed |
The AKS Kubernetes upgrade — control plane and every node pool — runs only as the governed UpgradeCluster Hosting/InstanceAction on the control instance, behind a mesh approval; never an ad-hoc az aks upgrade, az aks nodepool upgrade or kubectl. ONE approval covers the whole upgrade: control-plane minors chained one at a time, every pool upgraded once straight to the final version, the pool hosting the portals last, a health gate before each pool, and a stuck portal roll refuses it. Mechanism: hosting-aks-upgrade (operator), Hosting/ClusterUpgrade (MeshWeaver.Plugins — plan, approval, executor), the UpgradeCluster lane in Systemorph/Memex aks-ops.yml. |
in force | 2026-09-25 | maintainer |
module-sync-per-manifest-hash |
An instance ALWAYS syncs every module it has; nothing holds a whole Space or partition behind a per-identity seal. Each module is judged alone by the content hash of its tree — the moduleVersion its manifest.lock states once it is settled, recomputed from the tree so that an unsettled lock is never read as "unchanged": unchanged ⇒ nothing written; changed ⇒ it syncs to the incoming commit; a declared platform floor above the running platform ⇒ that one module is declined, loudly and by name, and holds no sibling. The seal decides only whether a NodeType ADOPTS prebuilt bytes or COMPILES from the synced source — never whether, or at which commit, sources arrive. Manual: Doc/Architecture/ModuleSyncPerManifestHash. |
in force | 2026-09-25 | maintainer |
package-min-mesh-version |
"source must ask for which min version. any version > min is used. normally we attempt to install latest" A package version declares minMeshVersion, and an installation uses it only when its running platform is at or above that floor. Only a floor that is COMPARABLE with the running version and strictly above it holds, decided by the ONE comparator PlatformFloor (run numbers where both sides have one, else the numeric core). A floor that cannot be ordered, an unknown running version or a local -ci.0 build proceeds as advisory, which keeps the 2026-09-07 ci < rc < clean trap closed. A held update keeps the installed version running and files ONE blocking ticket per held state through the control inbox (package-update-held). It never holds a platform roll. Replaces R2's "measured, never declared" for version selection. Owed: phase 2, where the registry keeps older versions with their floors and installers and GitSync pick the newest satisfiable one; and the MeshWeaver.Plugins control-plane intake of package-update-held as a blocking triage item. Manual: Module Adoption Policy → R2: the declared floor. |
in force | 2026-09-29 | maintainer |
init-timeout-retires-activation |
A hub that demand routing re-creates (WithReactivationOnDemand — every per-node hub) whose DataContext initialization TIMES OUT is disposed and logged at Error, never latched FAILED for the life of the process; the NEXT ACCESS re-creates it. No timer and no background retry: the parked backlog is answered terminally (ErrorType.Failed) so no re-ask latch retries on its own. A non-transient init FAULT keeps the latch. Mechanism: DataContext.SettleInitializationGate; account in What the DataContext Init Time-Box Bounds. |
in force | 2026-09-25 | maintainer |
area-view-reopens-on-deadline-miss |
A layout-area view whose stream fails with a transport DEADLINE MISS (the owner did not answer within the 30 s response deadline) opens a FRESH area stream — a new subscription to the owner — at most once per 30 s per open view, so it renders the owner's next frame instead of staying on "Area unavailable" until a reload. Only a deadline miss re-opens; every other error stays terminal, and no deadline is raised. The router's verdict for a bare timeout stays terminal (ErrorType.Failed) for every other path. Mechanism: AreaStreamReopen.ReopenOnDeadlineMiss + AreaErrorClassifier.IsDeadlineMiss (MeshWeaver.Layout); account in Error Propagation & Wedges → A deadline miss re-opens the view. |
in force | 2026-10-01 | maintainer |
dependent-suites-gate |
RETIRED — superseded by core-merge-never-blocked and one-promotion-gate. Was: a core change reached main only after MeshWeaver.Plugins' suites passed against the candidate, on every merge-queue entry, failing Consolidate test results on silence (measured cost: MeshWeaver#5807 green at 13:21Z, ejected 17:21Z on "did not answer within 42 min"). The advisory pull-request run and the verdict machinery remain; see One Promotion Gate. |
retired | 2026-09-25 → 2026-09-27 | maintainer |
core-merge-never-blocked |
SUPERSEDED by dependent-suites-affected-gate. Was: a core pull request merges on core's OWN required checks; no required context waits on MeshWeaver.Plugins; the dependent-suites run on a pull request is advisory (label dependent-suites, or a declared Pairs-with: Plugins counterpart). The merge queue on main stays off. Manual: One Promotion Gate. |
superseded | 2026-09-27 → 2026-10-08 | maintainer |
dependent-suites-affected-gate |
A core pull request PROVES it does not break MeshWeaver.Plugins before it merges (MeshWeaver#2689): every non-fork pull request asks Plugins to build and run, against the candidate (its fresh merge with main), exactly the suites and in-mesh Tests areas the diff can reach — selected PER REALM (one package, or the hosts) from the compiled and import graph, each with the edge that pulled it in, every unreached realm named as not selected — and Consolidate test results fails unless that verdict is green. Only what is affected runs: a selection that cannot be computed is RED naming why, never a full run; the once-a-day run stays the only full run. A measured break merges only when DECLARED in the body (Breaks-plugins: <realms> — <what> — counterpart Systemorph/MeshWeaver.Plugins#<n>; semver: <major|minor|ceiling>), the counterpart is green against the same candidate, and the semver consequence is in the diff. A fork pull request is the one event exemption, printed by name. Manual: Cross-Repo Pair Gate → "The dependent's suites run against the candidate". |
in force | 2026-10-08 | maintainer |
build-latest-green |
Every CI consumer and the image build take the NEWEST GREEN core main build (the resolver's newest set with a sealed platform trio — promoted, verified, baked — resolved by identity tag whether or not it is armed) together with MeshWeaver.Plugins main HEAD; a red core main falls back to the last green build, never forward into red. Plugins pull requests follow the newest set by default (the main-passed ceiling is opt-in, label platform:main-passed); the freeze variable MW_PLATFORM_REF stays for incidents. A burst of merges coalesces to one build of the newest heads; a run in flight is never cancelled. Manual: One Promotion Gate. |
in force | 2026-09-27 | maintainer |
one-promotion-gate |
The fleet rolls only to an ARMED set, and main-cd arm arms (the portal's <version> tag, the <line>-latest pointers, the release event) only the newest promoted set, newer than every armed one, whose PLATFORM verdict is green: the platform's own tests (promoted only from a green required check), that run's compatibility ladder (the published module set links against the image), and control running it (platform-deploy-control-first). No MeshWeaver.Plugins dependent-suites verdict is read (platform-module-deploy-separate; it decided arming until then). A ladder still running or a control not yet on the set waits, a red ladder is refused, a newer green set supersedes both; an override is a dispatch input carrying its reason. Platform delivery (promote, verify, bake, delivery-verdict) never waits on it. Every set is stamped with its commits (promotion record with full core_sha/plugins_sha, the release event's coreSha/pluginsSha, and the portal's staging-<core7>-<run id> tag that arm writes the version from; the <core7>-p<plugins7> host pair tag is retired and survives only on sets promoted before its retirement) so containment is answerable by ancestry. Manual: One Promotion Gate, Platform and Module Deploy. |
in force | 2026-09-27 (platform verdict: 2026-10-05) | maintainer |
platform-deploy-control-first |
Every platform build that passes the control image's own acceptance becomes the control instance's next image in the run that built it (memex-control:<version> + line pointers, never backwards — main-cd control-first), waiting for no arming, Plugins verdict, seal or module publication. The control record is Continuous with no rollGate, so that roll needs no approval; any OTHER roll of control still does. The fleet is offered a build only once control RUNS it (core commit contained by ancestry) and answers /health 200 — the control half of arm's platform verdict. Control is declared once, in .github/control-instance.json. Manual: Platform and Module Deploy. |
in force | 2026-10-05 | maintainer |
platform-module-deploy-separate |
The platform deploy ships the host image only — the core platform plus boot-time infrastructure modules (storage backends, the instance gate, and for control its fleet-control and self-update modules) — and never packs, bakes or seals a module, nor gates on a module's tests or verdict. Modules publish on their own lanes and every instance auto-updates them independently of any platform build, seal, pair or framework identity (packages-auto-update). What still guards a platform roll: its own tests and the compatibility ladder (the published module set links unchanged against the image); a module that needs a newer platform is held by its floor and blocks nothing. Owed: the image's non-boot module seeds (AI, Blazor.Chat, Mcp, Markdown.Export, Markdown.Collaboration, Blazor.Graph, Blazor.EntityViews) leave, under a boot-time declaration and a shrink-only ratchet, once a fresh instance lands them from the registry before readiness. The host pair tag is retired (Done, manual item 5). Manual: Platform and Module Deploy. |
in force | 2026-10-05 | maintainer |
control-first-never-silent |
One broken control instance must never freeze the fleet silently. When nothing is armed and control has been behind the newest build it was given for longer than platformLagBoundMinutes (the same bound and clock as control-always-latest, walked back to the first build control contains), main-cd arm goes RED, and its sentence names control's own self-update verdict. A failing self-update (SelfUpdateVerdict.IsFailure) is public on every instance: the census-tagged self_update entry on /health reads Degraded (never Unhealthy, no probe tag), and Admin/UpdatePolicy.lastCheckFailed is set. A 200 from the control inbox is reported as stored, never as a roll. Control-first itself is unchanged, and an arm override stays the maintainer's call. Manual: Platform and Module Deploy → A frozen fleet is red. |
in force | 2026-10-08 | maintainer |
control-always-latest |
The control instance always runs the newest platform build it was given and the newest compatible publication of every module it has installed. Lag past a declared bound is an ALARM: the platform half is core control-always-latest.yml (RED + the one control-lag issue; an unreadable control is lag, never a pass; bound platformLagBoundMinutes in .github/control-instance.json), the module half MeshWeaver.Plugins FleetTarget.ControlBreaches (a fleet-behind-target triage item per module behind a compatible served version past the bound; a floor-held module is not lag). Manual: Platform and Module Deploy. |
in force | 2026-10-05 | maintainer |
paired-change-sets |
A change spanning core and MeshWeaver.Plugins is built and tested TOGETHER before either half merges: the core pull request declares Pairs-with: Systemorph/MeshWeaver.Plugins#<n>, the Plugins one Core-ref: Systemorph/MeshWeaver#<n>, and the dependent suites run against the core candidate plus the Plugins head. Merging stays per repository, core first and never waiting; the Plugins half goes green only once a green core build contains the core half (checked by ancestry). Manual: Paired Change Sets. |
in force | 2026-09-27 | maintainer |
content-neutral-push-keeps-run |
A push to a pull request that changes nothing the pull request AUTHORED — a merge of main, regenerated generated files — never cancels the run testing that content; the new head adopts its verdict. Only a push that changes the authored diff supersedes. Mechanism (MeshWeaver.Plugins): pr-supersede.yml + scripts/ci-change-set.py. |
in force | 2026-09-27 | maintainer |
self-update-record-authoritative |
When a deployment record declares the platform image's update policy (rendered as SelfUpdate__DefaultPolicy, with SelfUpdate__DefaultPattern), that declaration is AUTHORITATIVE: at every start the self-updater converges the instance's Admin/UpdatePolicy policy and pattern to it, touching no other field. With no policy declared the configuration only seeds a new node, and an existing node belongs to the instance's admin. Mechanism: UpdatePolicyNodeType.ConvergeToDeclaration (memex/Memex.Portal.Shared); account in Why the Fleet Stopped Rolling Itself. |
in force | 2026-09-28 | maintainer |
oauth-bounded-live-credentials |
The MCP OAuth token exchange keeps a BOUNDED number of live credentials per (user, client_id) — the N newest, N configurable as Mcp:OAuth:MaxLiveCredentialsPerClient, default 5 — and evicts the oldest beyond it, logging each eviction. Not one per client: processes of one installation share a client_id, and one-per-client made each new sign-in revoke every sibling session (#5074). Mechanism: OAuthCredentialEviction (Memex.Portal.Shared); manual: MCP Authentication. |
in force | 2026-09-25 | maintainer |
self-grant-restored-at-logon |
The platform MAY re-create a user's MISSING own Admin grant on their home partition ({user}/_Access/{user}_Access), at that user's logon, as System on their behalf — only after explicitly authorizing that the logging-on identity is a real person and the home is their User node, only when the home holds no assignment naming them (a denied one included), as a create, never an upsert, and logged at Warning. It never grants anything else. Mechanism: RestoreSelfGrantLogonAction (MeshWeaver.Graph), RestoreSelfGrantLogonActionTest; manual: Logon Actions. |
in force | 2026-10-09 | maintainer |
oauth-idle-expiry |
A token the MCP OAuth exchange minted (label OAuth: {client_id}) EXPIRES once it has gone unused for 30 days, measured from its last use (LastUsedAt) or, if never used, from its creation: validation refuses it (stage oauth-idle-expired) and the expiry sweep deletes it. Tokens a person mints by hand are never subject to it. It ends the accumulation of one-per-registration slots a loopback client leaves behind, without touching the supersede key. Mechanism: OAuthTokenLifetime (MeshWeaver.Mesh.Contract), applied by ApiTokenService.Validate and ApiTokenVerdict; manual mints refuse the reserved OAuth: label; OAuthTokensExpireWhenIdleTest; manual: MCP Authentication. |
in force | 2026-10-09 | maintainer |
bake-gate-readiness-only |
A ROLL GATE — today the NodeType bake gate, nodetype_bake — holds READINESS only and never fails the startup probe. The startup probe proves only that the process booted; the gate's verdict is read by /ready alone, so a refusal stalls a roll (the pod stays alive, out of the Service) and never kills anything, and a restarted previous-image pod keeps serving. /health still runs and prints the gate; its status excludes it. The startup-critical checks (db_version, PostgreSql) stay on the startup probe; required_modules is a roll gate too (required-modules-readiness-only). Mechanism: ProbeEndpoints.RollGateTag + ServiceDefaults.TagRollGates, chart invariant 10b, RollGateReadinessOnlyTest; manual: The Bake Gate Only Stalls a Roll. |
in force | 2026-09-26 | maintainer |
required-modules-readiness-only |
The required-modules check (required_modules) is a ROLL GATE under the same rule as bake-gate-readiness-only: its Unhealthy verdict (a required module the image should ship is absent, or a present one did not install against this platform) stalls the roll and keeps the pod out of the Service, and never fails the startup probe or liveness. Its Degraded verdict (a store-delivered module not here yet) is a 200 and holds nothing, as before. /health still runs and prints it; its status excludes it. Core applies the tag by name, whatever the host's registration carries. Mechanism: ProbeEndpoints.RequiredModulesCheckName in ServiceDefaults.RollGateChecks, RollGateReadinessOnlyTest; manual: The Bake Gate Only Stalls a Roll. |
in force | 2026-09-26 | maintainer |
silos-pool-autoscaler-max |
The silos node pool (every portal) has its cluster-autoscaler maximum raised by one, 4 → 5, so a portal roll's surge pod is not left Pending while the public instance's KEDA scale-out holds its maximum. Node-pool bounds change only through the governed InfraDeploy action over Systemorph/Memex infra/estate.bicep — what-if first, then an approved deploy — never az aks nodepool update. Owed: the Memex change declaring the pool, the approved deploy, and the agentPools write grant for hosting-operator (the same Azure Kubernetes Service Contributor Role grant UpgradeCluster owes). |
proposed | — | maintainer |
governed-action-preflight |
A governed operation (a Hosting/InstanceAction) runs EVERY step under the system identity from its first step, and at PLAN time — before it parks for approval — verifies that each step is feasible: the rights it needs as system, and the scale bound of the mechanism each step uses. A step known to exceed its bound, or one the system identity cannot perform, is REFUSED AT PARK with the reason, never discovered partway through a destructive run. A whole-space deletion drops the partition as ONE teardown (PartitionTeardown.TearDownPartition), never a per-node recursive delete of its content. Manual: Partition Teardown → The direct teardown. |
in force | 2026-09-27 | maintainer |
severity-closes-on-verification |
A sev:B or sev:H issue closes on post-roll production verification of the running portal, never on the merge that lands its fix: a merge puts the fix on main, and what the label gates is whether the defect is gone from production. sev:M, sev:L, chore, enhancement and documentation issues close on a merge as before. Cited by the Closing keywords (no accidental close) gate, which refuses a body whose closing keyword targets an issue carrying either label. Ratification owed — filed for triage as rbuergi/Feedback/20260922-ratify-severity-closes-on-verification-policy, which carries the measurement that six merges closed a sev:H in the two days before the gate existed. |
proposed | — | — |
internal-code-review |
Pull requests are reviewed by the internal GLM-5.3 reviewer of the PR steward (MeshWeaver.Plugins Governance/PullRequestSteward), posting through the systemorph-com App; GitHub Copilot code review is retired from every repository. The steward reviews every in-scope, non-fork pull request and communicates only with global admins. Gate: check-review-answered.py accepts both reviewers until Copilot's rule is gone everywhere. Superseded by copilot-code-review. |
retired | 2026-09-27 | maintainer |
copilot-code-review |
GitHub Copilot code review is the fleet's pull-request reviewer again, replacing internal-code-review. Every repository's ruleset carries copilot_code_review with review_on_push: true (review_draft_pull_requests: false), so Copilot reviews every head: every push to a non-draft pull request starts a fresh review round, automatically, in all eight repositories' rulesets (MeshWeaver 2128472, MeshWeaver.Plugins 19008635, Memex 21038115, Crm 21741681, Education 19153714, Reinsurance 20302588, SocialMedia 20302587, Manufacturing 20625978), and nobody hand-requests a review. Amended: the ruleset carried review_on_push: false under review-once-per-pull-request and was set back to true on the amendment date below, so Copilot re-reviews after every code change. internal-review is not a required context anywhere. The gates accept a Copilot review: Automatic review answered takes a landed Copilot review, and the stage gate and the arm gate take a landed Copilot review of ANY head of the pull request (check-review-answered.py copilot_review_of_pull_request — the gate half of review-once-per-pull-request), so a push never waits for its own new review to land before the gates count the pull request as reviewed. An internal-review run, if one is still posted, is accepted as before. Every thread either reviewer opened still needs a person's reply. Owed: the internal reviewer's off-switch on the control instance, and the MeshWeaver.Plugins PrArming port of the same acceptance (Systemorph/MeshWeaver.Plugins#3088) merged and deployed on the control instance — until then the control plane arms nothing, and core #6063 (auto-arm disarm-only) waits on it. Manual: Review Findings Answered → Copilot is the reviewer again. Its per-head review (review_on_push: true) is in force again; review-once-per-pull-request keeps only its gate half. |
in force | 2026-10-07 (review on every push restored: 2026-10-10) | maintainer |
review-once-per-pull-request |
Now the ANY-HEAD GATE policy only — its original rule, "a pull request gets ONE automatic review, not one per push", is reversed by copilot-code-review (as amended): every push to a non-draft pull request gets a fresh review. What stays in force: every gate in check-review-answered.py (Automatic review answered, the stage gate, the arm gate) counts a landed Copilot review against ANY head of the pull request as "reviewed" (copilot_review_of_pull_request); a refusal is still not a review. With NO reviewer thread on the pull request the review must be COPILOT's — on any head (review-evidence-any-head). Every thread a reviewer opened, on whichever head, still needs a person's reply. Amended — the ruleset half is REVERSED: the rulesets' copilot_code_review rule ran with review_on_push: false under this policy and now runs with review_on_push: true again in all eight repositories (copilot-code-review), so Copilot reviews every new head of a non-draft pull request. .github/scripts/set-copilot-review-on-push.py reconciles the rulesets to that in-force value; the script that applied false is gone. The gate half stays in force: a landed review of any head still counts, so a push never waits for its own review to be re-requested. Owed: the same acceptance in MeshWeaver.Plugins PrArming. Manual: Review Findings Answered → One review per pull request. |
in force | 2026-10-07 (ruleset half reversed: 2026-10-10) | maintainer |
issue-text-name-scan |
Client names in the text a public repository publishes outside its tree (issue and pull-request titles and bodies, issue comments, review comments, review bodies) are scanned by a DETERMINISTIC Python script on a schedule (.github/scripts/scan-issue-text-names.py, .github/workflows/issue-text-names.yml), over every author including bots; no model runs. The term list is the CRM's: the script holds no names and submits the text to the CRM-owning instance as Governance/NameCheck requests with source: issue-text, which prove each line against the item it names and answer Filed, never a verdict. Findings go to that instance's private review and one masked triage item; the scan never edits or deletes. Manual: MeshWeaver.Plugins Governance/ClientNameGate → Issue and pull-request text. |
in force | 2026-10-09 | maintainer |
committed-text-names-from-crm |
A check that a repository's committed files name no client takes its term list from the CRM, never from a secret, a file or a build: no CONFIDENTIAL_TERMS-style denylist exists anywhere. The caller is core's shared .github/scripts/scan-issue-text-names.py --commit-files <pathspecs> (fetched, never copied), which submits the named files of the commit at HEAD as Governance/NameCheck requests with source: commit, the full sha and the live runId to the CRM-owning instance (NAME_CHECK_URL / NAME_CHECK_TOKEN). That instance proves every line is in the commit before it reads the CRM and answers Pass or Fail with masked positions; the caller prints file:line per hit and nothing else. Anything but an all-Pass is red, including a refused create (the missing namecheck.caller.grant) and a pathspec that selects no file. First caller: Systemorph/Memex build.yml over its publicly-bound tree. Manual: MeshWeaver.Plugins Governance/ClientNameGate. |
in force | 2026-10-12 | maintainer |
triage-owns-feature-vocabulary |
The feature: label vocabulary is a DECLARED list owned by bug triage — the Hosting/TriageFeatures node Hosting/TriageFeatureVocabulary in MeshWeaver.Plugins, changed by pull request. Agents may only pick from it: triage offers only listed feature: labels and LabelIssue refuses any other; a label merely existing in a repository is not enough, and an unreadable vocabulary admits none. Manual: MeshWeaver.Plugins Hosting/Triage → The feature vocabulary. |
in force | 2026-10-09 | maintainer |
secrets-write-only-entry |
No manual Key Vault access. Every secret the fleet uses is entered through a GUI in the app that owns it. The GUI is WRITE-ONLY: it sets or replaces a value, never reads or displays it back, and shows only status and a fingerprint. The vault has two identities: a READER (the pods' CSI identity: get only, per secret where the scope allows) and a WRITER (secret-writer: list, metadata, set, delete, recover, purge; never get), used only by the governed write path running as system. No human holds either. Mechanism: hosting-kv-set / hosting-kv-status / hosting-kv-state, the SetSecrets action; gate: check-manual-keyvault.py; manual: Secrets: Write-Only Entry, Split Identities. Owed: the secret-writer identity and its access policy (Systemorph/Memex infra/estate.bicep through an approved InfraDeploy), the reader narrowed to get, the Plugins status/lifecycle actions on the Deployments page, the Store payment-keys GUI, and the four operator steps that still read a value. |
proposed | — | maintainer |
governed-action-preflight |
A governed action (Hosting/InstanceAction) runs EVERY step with system credentials, and checks the rights and the scale each step needs at PLAN time, before it parks. A step that cannot complete (a right the system identity lacks, a volume a bound cannot cover) refuses the request at park, with the reason on the page. It never fails partway through a destructive run. Evidence: a DeleteSpace failed at its ninth step, on 31,138 per-node delete validations against a 25 s bound, after the space's grant and GitSync configuration had already been removed. Owed: the scale and rights pre-flight, in MeshWeaver.Plugins (in flight). Manuals: MeshWeaver.Plugins Hosting/AksOperationsViaActions, Hosting/DeleteSpaceAction. |
proposed | — | maintainer |
action-page-controls-and-queries |
An activity or action page is composed of platform UI controls in a real layout: cards, grids, and query controls that offer "show matching nodes". A SET of nodes is expressed as a mesh query (the top-level node plus scope:subtree), never as an enumerated listing dumped into a code control. The summary goes last. Owed: plan steps expressed as queries, in MeshWeaver.Plugins (in flight). Manual: MeshWeaver.Plugins Hosting/AksOperationsViaActions, "The run page". |
proposed | — | maintainer |
pr-babysitter-authority |
The fleet PR babysitter, the PR steward's heal/observe half, HEALS and never MERGES, and nothing irreversible runs unvalidated. Its deterministic pass only PROPOSES its one heal, with the evidence: a re-run of the failed jobs of a pull request whose every failing check died of infrastructure (runner, network, registry, artifact quota, or a stale review-gate verdict). Its validator agent approves or refuses each proposal against that evidence, with its reasoning. Only an approval runs: ONCE per head SHA, after the live head and the marker are re-checked, and behind a marker comment that carries the reasoning. It never merges, enables auto-merge, pushes, dequeues, approves a pull request or uses an administrator override. A waiting-for-platform pull request is read off its repository's label and re-run by that repository's own waker, never by the babysitter. Implementation owed: the validator's fail-closed EU enforcement (MeshWeaver.Plugins#2448) and the arming on the build instance (a governed Reconcile of its record). Shipped: MeshWeaver.Plugins#2439 (PrBabysitter, Hosting/PrWatch, agent pr-babysitter). Manual: MeshWeaver.Plugins Hosting/PrBabysitter. |
proposed | — | maintainer |
pr-babysitter-cadence |
The PR babysitter is event-driven, with a full fallback pass every 30 minutes. Its triggers are GitHub pull-request and check events and the arrival of a new sealed platform set. The events come from the ONE organisation webhook, and the control instance forwards them over the control lane. It runs on the BUILD instance only (opt-in configuration), one pass at a time. Implementation owed: as pr-babysitter-authority, plus the build instance's control-lane key, without which nothing is forwarded and the fallback pass is the only trigger. Shipped: core #5797 (forwarded events). |
proposed | — | maintainer |
code-review-eu-only |
Code review of pull requests — the PR steward's internal review and the PR-agent validator on the build instance — runs on GLM 5.3 through OpenRouter's EU endpoint https://eu.openrouter.ai/api/v1 ONLY, and FAILS CLOSED: a review that cannot run in the EU does not run. Mechanism (MeshWeaver.Plugins): the review model node Provider/OpenRouterEU/glm-5.3-review declares providerRouting (region EU; only Inceptron and Mistral; no fallbacks; zero data retention; no data collection), which makes it RESIDENCY-BOUND — reachable by node path only, credentialed only through its own provider (Provider/OpenRouterEU, which references the account key on Provider/OpenRouter rather than holding one), never substituted by another model, and refused before sending unless the endpoint is the EU host; OpenRouterProviderRoutingPolicy writes the pinned provider object onto every request. Governed setup: the standards pr.review-provider-eu.create and pr.review-model.bind. Owed: the two standards signed (the account is on OpenRouter's Business plan, which EU in-region routing needs), and the build instance's copy of the two nodes. Manual: MeshWeaver.Plugins Governance/PullRequestSteward § 6.2a. |
proposed | — | maintainer |
domain-config-apps |
Configuration and secrets live in the app that owns their DOMAIN — AI, Databases, Sign-in, Email, Payments, Integrations — never as ad-hoc panels or dialogs on the Deployment record page or elsewhere. A deployment's recorded shape is edited in the control instance's fleet app for that domain (/Hosting/{X}/Deployment/{id}: the record's block bound to the record, the domain's vault objects write-only, Apply files a governed Reconcile); what an instance changes about itself live is a tab of its own Admin app, under the same domain name. The record page links to the apps and carries no entry of its own. The AI domain's fleet app is named Models (/Hosting/Models; /Hosting/Ai redirects there). Owed: the six fleet apps (Models first) in MeshWeaver.Plugins Hosting/, and the record page's secret dialog and panels removed as each domain lands. Manual: Domain Configuration Apps. |
proposed | — | maintainer |
secret-actions-interim-actions-lane |
🚧 TRANSITIONAL. Until the secret-writer identity is provisioned AND every secret action runs in the in-cluster Job under it, the two VALUE-FREE secret actions — a generate-only SetSecrets (hosting-kv-set --generate, approved in the mesh like every SetSecrets) and a SecretStatus (hosting-kv-status, metadata only) — may run on the Actions executor (Systemorph/Memex aks-ops.yml) as hosting-operator (which holds get, list, set: the accepted interim risk is a writer that COULD read; the verbs never do and no value crosses the lane); each run says so on its node (writerIdentityNote). A pasted value and every lifecycle verb stay refused there, so a third-party-held secret has no write path until the exception ends. Manual: Secrets: Write-Only Entry → Interim: the value-free verbs on the Actions lane. Enforcement halves: the MeshWeaver.Plugins executor verdict (HostingOperator.ActionsLaneSecretVerdict, Plugins#2528, OWED until it is merged and rolled onto the control instance, when this row becomes in force) and the Memex aks-ops classifier (Memex#613, shipped). |
proposed | — | maintainer |
operations-instance-per-estate |
Every estate has the same shape, 1:1: exactly ONE operations instance, which holds BOTH the control role (the Deployments/* records, InstanceActions and their approvals, the signed inbox, triage, the fleet watch, the ops-lane dispatch) and the build role (the build queue, its scheduler and admission, the PR babysitter, build Jobs), plus any number of working instances. A separate build instance is retired through the generic migration, and the control role cuts over onto the instance the estate's records name as its operations instance; the cutover is decided. Exactly one inbox consumer per estate at every step. Owed: the build role's code and chart gaps G1–G10 (MeshWeaver.Plugins Hosting/OperationsInstance §7), then each estate's cutover acts (its records repository's cutover pull request). Manual: MeshWeaver.Plugins Hosting/OperationsInstance. |
proposed | — | maintainer |
bug-fix-eu-only |
The 24/7 bug-fix pool runs agent rounds on EU-routed models ONLY, and FAILS CLOSED: a variant whose dataResidency is not Eu is never eligible (there is no approval path for a non-EU variant), and a bug for which no EU variant is served on its estate is QUEUED — visibly, "queued — no EU variant served on this estate" — and never run on the agent's own or the deployment's default model; no thread round is dispatched for it. Mechanism (MeshWeaver.Plugins): BugFixPool.Assign / Ineligible (eligible only when dataResidency is Eu) and the pool's re-drive gate. Owed: that change merged (MeshWeaver.Plugins#2595) and rolled onto each estate's control instance, when this row becomes in force; and each estate's EU route (ai.openRouterEU) so a variant is served. Manual: MeshWeaver.Plugins Essentials/BugTriage → "The bug-fix pool". |
proposed | — | maintainer |
packages-auto-update |
Every installed package updates by itself as soon as a newer COMPATIBLE version is published to the registry: the only inputs are "a version was published" and "its declared platform floor ≤ the running platform". It depends on NOTHING else — not a platform image build, deploy or roll, not a publication seal or a seal for the running framework identity, not a green build of the platform or of the package repository beyond the package's own publish, not the platform's CD or fleet arming, not a per-identity prebuilt match, and not the partition's sync source (the module lane's sync-owned hold is retired). A fresh install is Auto; a record SEEDED with the old reminder-only default is migrated to Auto at boot; the only opt-outs kept are a per-package pin (None) or a policy a global administrator chose on the catalog card, each named in the log. An incompatible floor is declined by name and the running version keeps serving. What lands is activated through the module reload path (live, else exactly one automatic restart, no approval). Manual: Module Reload → "Auto-update". |
in force | 2026-10-05 | maintainer |
package-uninstall-request |
A platform admin uninstalls a package through ONE durable request (Admin/_PackageUninstall/{id}), in two phases. Phase 1, no confirmation: refuses by name a package not installed, a partition shared with another installed package or holding user data; retires the module (live, else exactly one automatic restart), closes the package's hubs, removes the install record, blocks every unattended re-install, and records exactly what phase 2 would destroy. Phase 2, the irreversible drop of the partition storage and its registry record through the platform's governed partition teardown, runs only after the REQUESTER repeats the partition name, recorded with who and when; without it the package stays uninstalled with its data retained. Owed: the MCP uninstall_package tool (MeshWeaver.Plugins), and a package-page entry. Manual: Package Uninstall. |
proposed | — | maintainer |
module-reload-request |
Any authorised caller (a platform admin, an agent through MCP, the platform's own watchers) can ask an instance to reload one module, or all, through ONE durable request (Admin/_ModuleReload/{id}). The instance resolves the newest compatible published version — its declared platform floor against the running platform, never a seal or a green platform build — lands it through the existing landing path, activates it live when the module can be swapped and otherwise by exactly one automatic restart through the self-update restart path, with no governed activity, no approval and no confirmation, and reports on the request node the version found, what landed, how it activated and what every replica loaded, or RED with the reason. Owed: the MCP reload_module tool and the fleet-target intake's switch from RefreshModules (MeshWeaver.Plugins), and the live loader's swap registering IModuleLiveActivation (MeshWeaver#6121, slice 2). Manual: Module Reload. |
proposed | — | maintainer |
instance-reboot |
ONE operation brings an instance to a known-good, NEWEST state: a durable request (Admin/_Reboot/{id}) runs, in order, Sync (every GitSynced module source at its branch HEAD, the git_hub_sync op=update import), Modules (the newest COMPATIBLE published version of every installed module, the module-reload landing), Image (the newest image the instance's update policy admits and its availability gate clears), ONE roll/restart that activates all of it, and Verify (every process booted after it: the critical-namespace bake, pending module activation, content types, and a thread-start smoke check — a process that measured no smoke check is RED). Every step is reported on the request with a named reason when skipped or failed. A person's call (MCP reboot_instance, the Reboot button, a platform admin) IS the signature — no second approver, the requester recorded; the instance's own watchdog files it without approval ONLY on the explicit wedge predicate (repeated load/binding faults on thread start, or a critical NodeType continuously in compile Error), at most once per interval, never twice for evidence a reboot already failed to clear, and every firing or refused firing is a Critical alarm. Owed: the MeshWeaver.Plugins half (the MCP tool, the deployment page's button and the Reboot InstanceAction kind, the AI module's thread-start smoke check and wedge reports). Until the smoke check is rolled onto an instance, every reboot there ends Failed at Verify by design — verification that cannot measure fails closed — and the verdict says so by name ("no smoke check was measured (none registered on this host)"), so the merge order is this core half first, the MeshWeaver.Plugins half right after, and a reboot is trusted green only once both are live. Manual: Instance Reboot. |
proposed | — | maintainer |
exact-release-surface |
The portal type surface the release gate links a landed module against belongs to ONE release: it is published at _releases/_surface/<release-version>, measured on that release's portal image, carried byte for byte to the clean version, and it is the ONLY surface the gate reads — no fallback to another release's or to a publication's. The platform-surface.json inside a content publication is descriptive and read by no gate; the shared _current pointer decides content, never a per-image fact. Chosen over shared canonical surfaces with producer provenance and ordering. Manual: Sealed Publication Reads, "The release gate reads the surface of the release it gates". |
proposed | — | maintainer |
ci-two-pools-priority-queues |
CI runs on exactly TWO runner pools split by technical need — plain (vars.MW_RUNNER) and Docker (vars.MW_RUNNER_DOCKER) — sharing ALL capacity; no pool, label or PriorityClass is reserved for a kind of work. Priority comes from the control instance's CI queue (Hosting/Queues/operator/ci), whose tiers express > trunk > gate > pr are dispatched in that order with a measured, relative share of the pool kept free for express; the order is mesh state, re-orderable by a platform admin through MCP (queue_list, queue_reorder). CI never depends on the control instance to run: every path to the queue fails OPEN, loudly. The SAME queue model carries every control-plane agent thread (reviews, fixer hand-offs, bug-fix and triage rounds) on Hosting/Queues/operator/agents with NO LIMIT: no pre-set concurrency or share — only a live provider refusal (429/402/quota) holds work back, by backoff on that (model, upstream) pair; this binds the per-round agent admission too (MeshWeaver.Plugins AI/AgentAdmission: no relative per-lane share once a pool is measured exhausted — a slow, stalled or round-capped round holds nothing back); executors scale with KEDA on queue depth. Owed: the removal of that per-lane share from AI/AgentAdmission (MeshWeaver.Plugins#2896 — until it merges, the share still applies); the queue live on the control instance and MW_BUILD_QUEUE=dispatch in MeshWeaver.Plugins, when this row becomes in force; then the retired trunk and gate scale sets removed (Systemorph/Memex). Manual: Runner Pools and Dispatch Queues. |
proposed | — | maintainer |
review-then-suites |
RETIRED — superseded by suites-parallel-with-review. Was: every pull request in the fleet moved REVIEW FIRST, then the suites, then the arm: the stage gate (node-repo-stage-gate.yml, default review-before-suites: true, with its stage-advance.yml listener) holds the expensive suites until the head's automatic review landed and every finding is answered; the suites then test a fresh merge with the current main (suites-test-fresh-merge); and auto-merge is armed only when the review is answered AND every required status check of the base is green on the head (check-review-answered.py arm gate condition 4, ported by MeshWeaver.Plugins PrArming). Replaces suites-parallel-with-review (suites beside the review), which it reverted the same day; review-before-suites: false remains a per-caller opt-out that never changes the merge or the arm. Manual: Staged Pull Request Pipeline. |
retired | 2026-10-05 → 2026-10-07 | maintainer |
suites-parallel-with-review |
The expensive suites run BESIDE the automatic review: the stage gate (node-repo-stage-gate.yml, default review-before-suites: false) lets them start as soon as stage 0 is green, on a fresh merge with the current main (suites-test-fresh-merge). Merging and arming are untouched — auto-merge is armed only when the review is answered AND every required status check of the base is green on the head (check-review-answered.py arm gate condition 4, ported by MeshWeaver.Plugins PrArming). Back in force after review-then-suites (review first, then the suites) was retired — the reviewer takes one review at a time, so holding the suites behind it left runners idle. review-before-suites: true (with the stage-advance.yml listener) remains a per-caller opt-in. Manual: Staged Pull Request Pipeline. |
in force | 2026-10-07 | maintainer |
suites-test-fresh-merge |
Every suite job of a pull request tests the head merged onto the base branch's CURRENT tip, resolved when the suites start and reported in the run — never GitHub's refs/pull/N/merge as it stood at the push, and never a stale merge on a re-run. The merge is computed in the job (no push, no branch update, no new head or review); a head that conflicts with the current tip is red. Manual: Fresh Merge Under Test. |
in force | 2026-10-05 | maintainer |
review-evidence-current-head |
SUPERSEDED by review-evidence-any-head. Was: "reviewed, nothing found" only on a completed Copilot review of the CURRENT head, so a clean pull request pushed again after its review (a merge of main) went red ("NO REVIEW OF THIS HEAD") until someone re-requested the review. |
superseded | 2026-10-09 → 2026-10-10 | maintainer |
review-evidence-any-head |
"Reviewed, nothing found" is ONLY a completed Copilot review object on the pull request with zero findings — on ANY of its heads: a re-review on every push is NOT required, so a clean review followed by a push stays reviewed. Automatic review answered (check-review-answered.py evaluate, via copilot_review_of_pull_request) accepts no timeout, no Reviewer unavailable degradation run and no internal-reviewer review as that evidence, and a pull request with no Copilot review object at all stays red; a refusal is not a review. A pull request WITH findings is judged by its answered threads, whichever head they were opened on (review-once-per-pull-request). The review-waived label applied by a maintainer stays the escape. A generated-only App pull request stays not owed (provenance, not a claim of review). Manual: Review Findings Answered → Reviewed, nothing found. |
in force | 2026-10-10 | maintainer |
review-carries-over-clean-base-merge |
A pull request's internal review CARRIES OVER to a new head that differs from the last reviewed head only by a clean merge of the base branch: walking back from the head through merge commits only reaches a head with a real internal-review, every commit added since is a merge, and the pull request's own diff (merge-base..head) has the same patch-id as the reviewed head's. Then the stage gate and the arm gate judge the reviewed head's run (and the pull request's answered threads) for the new head, and the PR steward posts that verdict as the new head's internal-review instead of starting a review round. Computed from the repository, never from a commit message; anything unreadable, any non-merge commit, or any conflict resolution that changed the pull request's diff owes a fresh review. The gate logs the reviewed head, its check run and both patch-ids. Manual: Staged Pull Request Pipeline. |
in force | 2026-10-06 | maintainer |
sources-sync-on-push |
An instance takes a repository's sources on every PUSH to a sync source's configured branch, at the pushed commit (after), never waiting for a green or sealed build; each module is judged alone by ModuleSyncDecision.Decide (unchanged ⇒ nothing, changed ⇒ synced, a declared floor above the running platform ⇒ that module alone declined with both versions named). A periodic branch reconcile (GitSync:BranchReconcileMinutes, default 10) repairs a lost delivery by resolving each branch head and importing at that sha. A green workflow_run records the build and imports nothing. Manual: Sources Sync on Push. |
in force | 2026-10-05 | maintainer |
record-change-applies-itself |
A change to a Hosting/Deployment record reaches the pods without anyone filing or approving anything. Every Roll — CD's routed roll included — re-applies the CURRENT record at its tag (hosting-deploy over the record's render, after the tag's migration), never a bare set image that keeps the old environment. A record whose rendered values differ from every render a roll or reconcile applied gets ONE Reconcile the control plane opens itself, as system, with origin: record-change and the render's digest; it runs unattended exactly while the record still renders that digest (the record write is the governed act), a newer render supersedes a queued one, an identical render opens none, and no image tag is ever written. A render never SILENTLY drops a value the live release carries: hosting-deploy refuses, naming each path, until the record expresses it (e.g. its features, Memex#376) or it is removed deliberately. A hand-filed Roll/Reconcile stays the break-glass path. Manual: Hosting's Hosting/RecordChangeAppliesItself (MeshWeaver.Plugins) · Operating From the Portal. |
in force | 2026-10-07 | maintainer |
prune-requires-provenance |
A source import prunes a node only when the repository put it there — the node is in the partition's prior import manifest — in every pruning mode (FullReplace and Additive now prune the same set). A node created in a synced partition at runtime is never pruned by a source sync, and unknown provenance (no or an unreadable manifest) is never prunable. Manual: Sources Sync on Push → The prune. |
in force | 2026-10-05 | maintainer |
authorize-as-caller-execute-as-system |
A control-plane / system operation (an instance action and its approval re-plan, a governance pass, a sync, an operator step, a watcher acting on a request node) separates AUTHORIZATION from EXECUTION. The caller's access is checked EXPLICITLY — first when the request node is WRITTEN (its create/update is validated and refused by name, so an unauthorized request is never parked), and again immediately before executing (the requester or approver may have lost the right) — fail-closed, naming the missing permission. Then the operation executes as System (ImpersonateAsSystem, composed via RunAsSystem): reading the records it acts on, rendering plans, writing results; it never depends on the caller's RLS reach mid-execution. Impersonation is never a substitute for the check, and application writes on a user's behalf still carry the user's identity. Manual: Authorize as Caller, Execute as System. |
in force | 2026-10-08 | maintainer |
declared-not-derived |
Anything the platform cannot run without — language models and providers, agents, queues, policies, credential references — is DECLARED (deployment-record config or committed content), and the declaration is the source of truth. A boot-time importer or reconciler never deletes a declared item because a module, plugin or registration did not contribute it on this replica: an absent contributor is not a removal. Pruning removes only what the declaration itself dropped. A change that makes a critical catalogue depend on module wiring alone is a defect. Carried as the shared rule block of the same id in AGENTS.md — in this repository (the hub) now. Owed: the six spoke copies (step 2 in .github/shared-rules.json) and widening that block's required-in to all seven repositories (step 3); this row becomes in force when both have landed. Manual: Declared, Not Derived. |
proposed | — | maintainer |
approval-step-up |
Every approval act (a governance signature, an instance action's Approve, an operation request's Approve, the approval inbox's bulk approve) counts only with a step-up RECEIPT: sealed, single-use, bound to (approver, action path, plan/script hash) and minted only after a fresh strong authentication — always prefer a passkey: Entra ID step-up (prompt=login + a Conditional Access authentication context requiring phishing-resistant authentication) for Microsoft accounts, the portal's own passkey for every other account, TOTP only where no passkey is possible. The server checks and consumes the receipt; the page only starts the step-up. Declared per deployment record (Authentication:StepUp:*), off until declared; once on, never an approval without one. Manual: Approval Step-Up. Owed: the MeshWeaver.Plugins consumers and a roll; this row becomes in force when an instance has it declared and every consumer checks it. Refs #4305. |
proposed | — | maintainer |
registry-retention |
The meshweaver ACR keeps every unreferenced continuous artifact at least 30 days by age with NO build-count quota (--ago 30d, no --keep), and a referenced one — pinned, locked, or part of what a live installation runs — is never deleted whatever its age. The purge's definition and its enabled state change ONLY through the governed RegistryRetention InstanceAction on the control instance (registryMode: verify read-only and unattended; apply with the registry name typed back and one mesh approval), which runs .github/scripts/acr-retention-tasks.sh on the estate's operations lane as the operator identity — never an az call from a laptop. Enabling a purge additionally requires the record's pause lifted in a reviewed diff AND the newest lock-lane run on main green and recent; both refuse by name. Owed: the action kind (MeshWeaver.Plugins) and its lane step (Systemorph/Memex) merged and rolled onto the control instance, and the operator identity's grant on the registry's tasks. Manual: Pinned Image Retention → The governed path. Refs #3438. |
in force | 2026-10-09 | maintainer |
fleet-registry-bounded |
cr.meshweaver.cloud does NOT grow without bound: it gets a lifecycle rule, and that rule never deletes anything a lock, a committed pin, a sealed set, an active build or a live installation references. Because distribution has no lock, the rule's protected set is DERIVED each run from the consumer inventory first and the run fails closed when that inventory cannot be read completely; it is applied through the governed infrastructure path (an InstanceAction, what-if first), never by hand. Until that cleanup exists the registry's record keeps cleanupAuthorized: false. Manual: Fleet Registry Retention → The decided rule. Refs #4230. |
in force | 2026-10-09 | maintainer |
review-admission-cost-limit |
The bound on the control plane's pull-request review rounds, whenever one is in force, is a COST LIMIT, not a throttle: a review round runs only on a slot recorded on the triage ledger (Hosting/Triage/Status). When no slot can be claimed — the bound is reached, or the claim cannot be established because the ledger cannot be read, created or written — the head is QUEUED, durably on its triage item (a node, never an in-memory queue), and admitted when a slot frees, oldest first. A claim that cannot be established fails CLOSED; no round is ever started unbounded. Narrows ci-two-pools-priority-queues, which decides WHEN there is a bound (only under a live provider refusal), not what a failed claim does. Mechanism: MeshWeaver.Plugins ReviewAdmission.ClaimSlot / FailClosed. Owed: the Plugins change (Systemorph/MeshWeaver.Plugins#3238) merged and rolled onto the control instance. Manual: MeshWeaver.Plugins Hosting/ReviewRoundCapacity → The bound is a cost limit. Refs #6104. |
proposed | — | maintainer |
payment-endpoint-governed-task |
MeshWeaver may CREATE its own payment-provider webhook endpoint, or ADD the required events an existing one lacks, through a governed task a global admin triggers (Store/Maintenance task EnsurePaymentEndpoint; removal of its own endpoints only via DeregisterPaymentEndpoint): dry run by default, executed only with Execute plus the account mode confirmed, only while the payment-path audit reads NotDelivering, only for this instance's own hook URL and the provider's required events, additive only (never removing an event, never disabling or re-enabling), refused when the account lists more than one endpoint for the URL or endpoints for other hosts. A newly disclosed signing secret is shown once to the person who ran the task and stored nowhere. The payment-path audit stays read-only and no agent calls the provider with an account key. Contract: IPaymentProvider.EnsureDeliveryEndpoint / RemoveDeliveryEndpoint (default NotSupported). Manual: The Payment Provider Contract → Registering the delivery endpoint is a governed write. Owed: the MeshWeaver.Plugins half (Stripe implementation + the two tasks, Systemorph/MeshWeaver.Plugins#1881) merged and live. |
in force | 2026-10-09 | maintainer |
azure-repos-push-only |
An Azure Repos repository (dev.azure.com / {org}.visualstudio.com URL) is a GitSync target push-only first: mesh → repo, declared ExportOnly; every inbound operation (import, re-import, branch lookup, service hooks, pull requests) is refused by name, never silently skipped. The provider is DERIVED from the URL, never a stored field. The credential is the instance's workload identity federated to an app registration in the CLIENT's tenant, whose AzureDevOps:TenantId / AzureDevOps:ClientId are DECLARED per client tenant in that instance's deployment record — no secret is stored. Settled on MeshWeaver#5248. Manual: Syncing a Space with GitHub. |
in force | 2026-10-09 | maintainer |
image-closure-as-mesh-data |
An image's closure — what is in /app, per platform, per file with its SHA-256, plus the image's platform commit, version and framework identity — is QUERYABLE MESH DATA: an ImageClosure node the CD build writes per image it publishes, keyed by the image DIGEST (stable across retag and promotion; tags are an attribute), built from the bytes check-platform-reference-set.sh just accepted. Every gate that reads it states its COVERAGE (ImageClosureCoverage: N of M expected images measured, each missing one named NOT MEASURED) and never reads an absent record as clean. The producer-side shell assertion stays. Owed: the control plane's record allowlists the inbox target Admin/ImageClosures with secretConfigKey: Hosting:PlatformWebhookSecret. Manual: An Image's Closure, as Mesh Data. Refs #4066. |
in force | 2026-10-09 | maintainer |
customer-instance-governance |
On a CUSTOMER instance (its deployment record sets operator.customerGoverned: true), the governed actions the instance requests from its control instance are approved by the instance's OWN global administrators, attested by the instance over its signed per-deployment lane, under sole-maintainer-approval with the maintainer declared in THAT record (operator.maintainer): the approver is not the requester unless the requester is that declared maintainer. No administrator of the control instance is required, and a control-side approval does not stand in for the customer's. Records without the flag (every Systemorph-managed instance) keep the control-side rule unchanged. Mechanism: MeshWeaver.Plugins CustomerInstanceRequests + ActionsExecutor.ApprovalGate. Owed: the instance-side request and approval surface and the result return path for Logs/Sample (tracked on Systemorph/MeshWeaver.Plugins#2604). Refs Systemorph/MeshWeaver.Plugins#2495. |
in force | 2026-10-09 | maintainer |
model-settings-seed-and-ceiling |
On a customer-governed instance (its deployment record sets operator.customerGoverned: true AND its ai block names at least one model), the record's model settings are the instance's SEED and CEILING, never its value: every model the record names is seeded on the instance, and the instance's own global administrator may NARROW within them — switch a named model off, choose a named model per tier — and never EXCEED them. A model outside the record's lists is refused by name when a person writes it, hidden from the picker, skipped by tier and default resolution, and refused by a round; requiredDataResidency stays a fail-closed ceiling as before. The record wins: when it drops a model, the instance's choice of it is clamped on the next read and the record's own seed answers instead (consistent with self-update-record-authoritative). Every other record keeps the default reading, where the record's list IS the instance's. Rendered as AI__ModelGovernance=SeedAndCeiling (DeploymentPortalConfig.ModelGovernanceKey, reserved against extras). Mechanism: MeshWeaver.Plugins ModelGovernance / ModelCeiling. Decided on MeshWeaver.Plugins#2604. |
in force | 2026-10-15 | maintainer |
customer-instance-requests |
A customer instance may request exactly Restart, Recycle, Logs and Sample from its control instance; every other kind is refused by name. Rolls stay with the control instance: the self-update routed roll is governed there by the record's update policy, and a customer administrator cannot request an arbitrary roll. Refs Systemorph/MeshWeaver.Plugins#2496. |
in force | 2026-10-09 | maintainer |
node-tests-compile-only-off-linux |
With the IMAGE-shaped reference set (what CI and --latest use), the canonical node-test harness is COMPILE-ONLY off Linux, and that is the contract: compile-check.py answers compilation on any host; run-node-tests.py refuses an image-shaped set on a non-Linux host up front (exit 3, naming set and host) and never reports it as a build failure; a host-built sibling set (source-build) still executes on any host; the gate executes the suites on Linux, on the composed host (the portal image's /app with the tester CLI mounted beside it). No off-Linux execution mode for the image set is planned. Manual: Canonical Node Test Harness → The contract: compile anywhere; an image set executes on Linux. |
in force | 2026-10-09 | maintainer |
release-identity-from-registry |
Where the published bundle root carries no marker _releases/<version> for a candidate release, the release-availability gate takes that release's framework identity from the fleet registry's record plugins/releases:<version> — reached through a registry mount's declared artifact host, with that mount's key and no other. The publisher's marker file wins wherever it exists and the registry is then not asked; nothing is written to the root; every way of not knowing (no declared host, no record, a record for another version, an identity that is not one path segment, a failed read) leaves the hold in place. Manual: Plugin Bundles in the Registry → A release's identity on a registry-fed root. |
in force | 2026-10-11 | maintainer |
record-served-commit-on-install |
An install records the EXACT commit its source served (the registry's /api/plugins/files answer carries commit; an incremental update reads its manifest and its delta at that one commit), and the combo verifier checks the installed content against that recorded commit — never against the source repository's moving HEAD. A package whose source folder is absent at its recorded commit AND at HEAD, both listings complete, is reported retired, not verifiable on its own list and does not fail the verification; a combo whose every module is retired is never Green. Existing records are not backfilled; they restamp on their next update. Refs #3848, #6718. Manual: Combo Gate Wiring → The recorded commit is the reference. |
in force | 2026-10-13 | maintainer |
registry-fed-gate-reads-live-publication |
Where the registry named a candidate release's framework identity (no marker on the root), the release-availability gate judges the sealed set from each source's LIVE registry publication plugins/<source>:<identity>, read at the moment the gate runs — a package counts as sealed exactly when a bundle entry of that publication index names it — and never from the snapshot the running pod pulled at start. The release record is not changed and gains no field. A publication that could not be read is Indeterminate, never the snapshot instead; an absent tag is an established absence. Manual: Plugin Bundles in the Registry → The sealed set is the live publication. |
in force | 2026-10-13 | maintainer |
Cited by: Release Process · Pinned Image Retention · Fleet Registry Retention · An Image's Closure, as Mesh Data · The Self-Update Schema Wall · Issue Taxonomy and the Release Readiness Gate · Adding a Data Sync Needs a Global Admin · Thread Supervision · Access Control · Closing Keywords and Issue State · The Platform Compatibility Ladder · The Bake Gate Only Stalls a Roll · Deployment (AKS) · Secrets: Write-Only Entry, Split Identities · Module Adoption Policy · Declared, Not Derived · Approval Step-Up · Combo Gate Wiring.
🚨 A
proposedrow is not a weakerin force— it is an honest one. The first draft of this register listedrelease-blocker-gateasin forcewhile the standard that enforces it was still an unmerged pull request; the row was moved toproposednaming what was owed, and back toin forceonly once that standard had merged. That is the precise failure this page exists to prevent, committed in the page that defines the rule. If a row's mechanism does not exist yet, the row saysproposedand names what is owed.
Adding a policy here is cheap and reversing one is cheap. That is the point: a register entry can be changed in one place and every citation follows, which is exactly what a sentence copied into four files cannot do.