instar 1.3.1189 → 1.3.1191

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generatedFrom": "source-tree",
4
- "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
5
- "packageVersion": "1.3.1189",
4
+ "registrySha256": "5e49a5f5481c67c725c3bab997ab085370c5904e92e1ebf480d6105711e1e305",
5
+ "packageVersion": "1.3.1191",
6
6
  "guards": [
7
7
  {
8
8
  "ref": "docs/audits/phase-b/f10-triage.md",
@@ -1,5 +1,5 @@
1
1
  {
2
- "sha256": "5d762bbb3b25d0fb34ecaa317a638f1eab6af13dc6a22f7392c054800ccee190",
3
- "registrySha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
4
- "packageVersion": "1.3.1189"
2
+ "sha256": "f3973d47f7199ab0b06aa43bcb13b8034f61a88607b4da1ebf5aa436445670cc",
3
+ "registrySha256": "5e49a5f5481c67c725c3bab997ab085370c5904e92e1ebf480d6105711e1e305",
4
+ "packageVersion": "1.3.1191"
5
5
  }
@@ -56,6 +56,8 @@ Generated by `scripts/generate-standards-hierarchy.mjs` from the parent/child de
56
56
 
57
57
  - [The Body and the Mind](#the-body-and-the-mind)
58
58
  - [Structure Decides Alone Only on an Exact Match](#structure-decides-alone-only-on-an-exact-match)
59
+ - [Deferral = Deletion](#deferral-deletion)
60
+ - [Archiving May Never Mean Deleting](#archiving-may-never-mean-deleting)
59
61
  - [Close the Loop](#close-the-loop)
60
62
  - [Canonical Pipeline Operational Completeness — Accepted Intake Must Drain](#canonical-pipeline-operational-completeness-accepted-intake-must-drain) — *declared from Building*
61
63
  - [Keep the Doorway/Model Map Current](#keep-the-doorwaymodel-map-current) — *declared from Building*
@@ -126,7 +128,7 @@ Generated by `scripts/generate-standards-hierarchy.mjs` from the parent/child de
126
128
  - [Near-Silent Notifications](#near-silent-notifications)
127
129
  - [Bounded Notification Surface — no feature may flood the user](#bounded-notification-surface-no-feature-may-flood-the-user) — *declared from Building*
128
130
 
129
- *39 declared parent-child relation(s) across 23 parent article(s). The other 49 articles declare no parent.*
131
+ *40 declared parent-child relation(s) across 24 parent article(s). The other 49 articles declare no parent.*
130
132
 
131
133
  <!-- END GENERATED STANDARDS HIERARCHY -->
132
134
 
@@ -217,10 +219,19 @@ The Root says *enforce behavior in structure, not willpower.* This family is **w
217
219
  **Fails.** If the durable capture path is absent, fail closed: do not ship or cross the instance boundary with the obligation or lesson uncaptured.
218
220
  **Judgment-bound.** Accepted 2026-08-12 by operator ruling as NOT mechanisable: this article turns on whether an insight is worth preserving; a silently omitted insight leaves no trace anywhere to check against. A gate over the DECLARATION would detect a well-formed claim and not a breach, and building one would let a paperwork check be called enforcement — the defect this classification exists to prevent. The obligation therefore shifts from "build a check" to **make the judgment good**, and it is an obligation, not an exemption: (a) **context sufficiency** — the call must be exercised with the context it needs, since a judgment made blind is the failure mode; (b) **rating** — the call is logged with its context in the decision journal and periodically rated: how it performed, how it can improve, what context it was missing. Judgment-bound is not unmeasured; the mechanical check is replaced by continuous evaluation, not by nothing.
219
221
  **In practice.** Deferral feels harmless to the deferring instance because *it* still remembers; the cost lands on a successor who never had the context and cannot reconstruct it.
220
- **Applied at the shipping layer.** This is the substrate-level *why* beneath the **No Deferrals** standard (Shipping): there, a deferral without a tracked same-PR commitment becomes a regression; here, the deeper reason is that across an instance boundary the deferred thing is simply gone.
222
+ **Applied at the shipping layer.** This is the substrate-level *why* beneath the **No Deferrals** standard (Shipping): there, a deferral without a tracked same-PR commitment becomes a regression; here, the deeper reason is that across an instance boundary the deferred thing is simply gone. Tree node beneath it: *Archiving May Never Mean Deleting*, which carries this same conservation past the capture boundary — an insight that WAS captured is equally gone if the store holding it evicts it.
221
223
  **Earned from.** A PR deferred "lifeline auto-restart on server upgrade," and that exact gap produced a regression two days later (see *No Deferrals*).
222
224
  **Traces to the goal.** A self-evolving agent that defers its own learnings evolves nothing.
223
225
 
226
+ ### Archiving May Never Mean Deleting
227
+ **Rule.** Where a store bounds its own growth it may COMPACT, COMPRESS, or SUMMARIZE, but it may not DELETE agent memory. Rotation-with-deletion is disqualifying for any store holding what the agent knows. Summaries may be added for speed; they NEVER replace the material they summarize — **except as the execution of a permitted deletion**, where replacing sensitive source material with a summary is one lawful way to carry out an operator instruction or an erasure obligation (added round 3: worded absolutely, this clause forbade privacy-preserving replacement even where retaining the source was unnecessary and disproportionate). Replacement is therefore a FORM of the permitted deletion, never a fourth path around it — **and the replacing summary must itself OMIT the material the deletion required removed** (round 4: without that condition, "replace the source with a summary" is a lawful-sounding way to keep the very content an erasure obligation destroyed, which is worse than refusing the replacement outright). The permitted deletions are DELIBERATE ACTS OF A PRINCIPAL, categorically distinct from a system quietly forgetting on its own: one the operator explicitly requests, and one a lawful erasure obligation requires (a data-subject erasure request, a controller instruction, a legal order) — the second is named because erasure authority does NOT originate only with the operator, and an article that recognised only the operator would force a choice between the constitution and the law. **Dual-purpose records — the precedence rule, stated because the continuity test alone begs the question for them (external adversarial lens, 2026-08-22).** A record can be both memory-bearing and operational (a judgment rationale is provenance AND something a successor needs). The rule is not "classify it once": a bounded operational store MAY hold such a record and MAY age its copy out, but it may never be the ONLY copy of the memory-bearing content — that content must also live in a store governed by this article. Where two readers would classify a record differently, that disagreement is resolved by DUPLICATION, not by adjudication. **Subject, stated in the Rule because it decides a live collision.** This governs AGENT MEMORY — conversation history, learnings, relationships, knowledge; the record a successor instance needs in order to BE the same agent. It does NOT govern telemetry, audit trails, metrics, or coordination breadcrumbs, which are bounded on purpose and whose bounding *Observable Intelligence* affirmatively requires. The distinguishing test is whether losing the record costs the agent its CONTINUITY, not whether the record is useful.
228
+ **Tree placement.** **CHILD** — this is **a tree node under *Deferral = Deletion***, declared 2026-08-22 at ratification. That parent holds that an insight not captured NOW is gone, because the successor instance lacks the context that made it worth capturing. This article is the same conservation one step later in time: an insight that WAS captured is equally gone if the store holding it evicts it. Same loss, same reason — no successor can reconstruct it — different mechanism: omission versus eviction. A CHILD rather than a sibling because the parent states the conservation principle and this extends its reach past the capture boundary. It is deliberately NOT filed under *An Instar Agent Is Always a Multi-Machine Entity*: its subject holds on a single machine with no peers at all — a store that rotates history away violates it whether or not the agent is multi-machine — and filing it there would bind a general obligation to a condition it does not depend on.
229
+ **Fails.** If a store cannot demonstrate that its growth-bounding path preserves rather than evicts, fail closed on ADOPTING that store for agent memory: the work may proceed on a store that can, but memory may not be entrusted to a mechanism that forgets. A PERMITTED deletion — operator-requested or lawful-erasure-required — is never this failure; it is the permitted case, and the wording says "permitted deletion" rather than "operator-requested" because an external lens caught the narrower phrasing quietly re-closing the carve-out the Rule had just widened (round 3). **Erasure must PROPAGATE, and this is the condition that makes the duplication rule safe.** When a permitted deletion runs, it must reach every copy the duplication rule created — the memory store, any bounded operational copy still holding it, derived summaries, and search indexes. A duplication requirement that did not carry a propagation requirement would let this article preserve exactly the content an erasure obligation required destroyed, which is the reverse of the compliance problem it claims to help with.
230
+ **In practice.** **Distinct from *Observable Intelligence*.** That article's **Balanced by — Responsible Resource** clause requires audit trails to age out — bounded retention, never forever — and calls an unbounded observability store its own incoherence. Worded generally, "archiving may never delete" contradicts it head-on, and a reader could cite the registry for either position. The boundary is therefore part of this Rule rather than commentary beneath it: memory is conserved, telemetry is bounded, and the test between them is continuity rather than usefulness. Without that line stated in the Rule the two articles are each citable against the other, which is the both-sides-citable defect the registry's residual section exists to prevent. **Why this is affordable, measured rather than assumed.** The obvious objection is that "every machine holds everything" plus "nothing is ever deleted" is unbounded growth on every machine forever. Compression resolves it and the margin is not close. Measured on the originating machine 2026-08-21: 2,490 messages over 3 days, 3.0 MB raw, **0.4 MB gzipped — 7.5x, lossless.** At that rate a year of history is roughly 50 MB compressed and a decade under half a gigabyte. **The honest scope of that measurement (narrowed at ratification after an external lens called the extrapolation improper):** it covers MESSAGE TEXT on one machine over three working days. It does NOT cover attachments, search indexes, encryption overhead, the other memory kinds, or growth rates unlike those three days, and "affordable on any hardware" is therefore a claim about conversation text specifically, not a proof for all memory. What the number establishes is narrower than it first reads, and is stated at its real strength (round 3, external lens): for THIS sample — message text, this machine, three heavy working days — the size objection does not hold, by a margin large enough that a modest change in growth rate would not reverse it. It does NOT establish a bound for other growth rates, other memory kinds, or constrained hardware, and this article claims no unconditional bound. The obligation to keep memory does not rest on the measurement; the measurement only removes the reflex that the obligation is obviously unaffordable. **A claim that stood in the proposal was withdrawn before ratification and the withdrawal is recorded here rather than edited away:** the proposal argued the real cost was the container — that the coherence journal rotates-and-deletes and that its per-kind byte budgets were too small. Reading the rotation code shows both halves wrong: the byte figure is a rotation THRESHOLD with no total ceiling behind it, and a rotate-but-never-delete setting already ships on one kind today. A peer that falls past the tail window re-joins by snapshot-then-tail rather than losing anything. The container was never the obstacle. **Why the operator carve-out is load-bearing rather than decorative.** The stores that hold agent memory deliberately DO expire their transport logs, and the reason written into that code is erasure compliance: a never-deleting log would retain a person's details after they had asked to be erased. That is a genuine legal constraint and an unqualified "never delete" would collide with it. The carve-out is what lets both hold at once — the system never forgets on its own, and the principal may still ask for a deletion. So this article is compatible with the compliance requirement rather than in conflict with it — but it does not by itself DELIVER compliance, and the earlier draft claiming it did was over-claiming (round 3, external lens). Actual compliance depends on the propagation condition in **Fails.** above holding across every duplicate, summary, index and replica, which is engineering work this article requires and does not perform.
231
+ **Documented-only until.** `2026-09-22` — tracked as `STD-COUNTDOWN-archiving-never-deletes`. No guard decides whether a store deletes agent memory, and none is claimed. This lands as a `/spec-converge` cross-machine and integration reviewer question — "does any store here delete agent memory, and is what it deletes memory or telemetry?" — which is semantic authority, weaker than a lint and stronger than prose. A deterministic version is conceivable (enumerate the stores holding memory kinds, assert each declares a non-evicting retention posture) and is not built. **Sub-obligation countdown.** `2026-09-22` — tracked as `STD-SUBCOUNTDOWN-archiving-never-deletes`. This is a COUNTDOWN, not a resting state: the article is ratified and binding from 2026-08-22, and the label records that its enforcement is owed rather than built.
232
+ **Earned from.** 2026-08-21 (topic 52222), operator directive, ratified 2026-08-22: "archiving CANNOT mean deleting. We must be able to access message history at all costs." Raised while designing cross-machine conversation coherence, where the mechanism proposed for replicating history was one built to bound itself by eviction — the agent was about to entrust its memory to a container designed to forget, and the design passed six convergence rounds without anyone naming that.
233
+ **Traces to the goal.** The founding promise is one coherent, persistent agent. The registry's own Root section already supplies the reason this line falls where it does: destroying an agent's accumulated record is "an injury to a self," not a data incident. *Deferral = Deletion* protects the record at the moment of capture; this protects it for the rest of its life. An agent whose memory is bounded by eviction is not persisting through time — it is a sliding window that believes it is a self.
234
+
224
235
  ### Close the Loop
225
236
  **Rule.** Every loop the agent opens — a promise to a user, a feature shipped dark, an LLM gate deployed, a flagged issue, a hypothesis to revisit — must be durably registered and re-surfaced on a cadence until it reaches a *deliberate* close. Capturing it once is not enough; if no structure brings it back for review, it rots silently and is, in effect, abandoned. *"Untracked = Abandoned."*
226
237
  **Tree placement.** **ROOT / FOUNDATIONAL** — this standard declares no parent, and that is a stated position rather than an omission. It now carries merged subsections beneath it, which is exactly when its own placement had to be settled rather than assumed. Declared 2026-08-13 under the insertion-placement rule: a standard that is edited must say where it sits, because editing it is when its place in the tree is actually in mind. If a genuine parent is later identified, this becomes a child declaration — a root position is a claim that can be revised, not a permanent exemption.
@@ -453,15 +464,15 @@ The Root says *enforce behavior in structure, not willpower.* This family is **w
453
464
  **Per-feature posture (2026-06-12 widening).** The lease layer alone does not make ONE agent — the 2026-06-12 audit (topic 13481) found ~20 features that were individually correct and collectively machine-blind: preferences, attention items, jobs, sentinel voices, and links all silently assumed a single machine, because no review surface ever asked. The rule, per feature: **every feature with durable state, user-facing notices, or generated URLs must declare its multi-machine posture — though this article **does NOT own the posture-declaration obligation**, which belongs to *An Instar Agent Is Always a Multi-Machine Entity*, where the permitted postures are enumerated. Read the requirement to be robust across machines here; read what a posture may SAY, and that one must be asserted, there — replicated (named path) / proxied-on-read (named read) / machine-local by design (with the reason) — at review time.** Enforced through the side-effects review template §7 (Multi-machine posture, `skills/instar-dev/templates/side-effects-artifact.md`) and the spec-converge integration reviewer's mandatory posture check (`skills/spec-converge/SKILL.md`); the gap-closure work itself is `docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md`.
454
465
 
455
466
  ### An Instar Agent Is Always a Multi-Machine Entity
456
- **Rule.** Every feature, state surface, and design decision treats the agent as a SINGLE entity spread across many machines, BY DEFAULT. "Unified across my machines" is the default posture; "machine-local" is an EXCEPTION that must be justified with a concrete reason it *cannot* be unified (a login or credential that physically lives in one disk's keychain; a hardware-bound resource; an operator-ratified exception) — never a default chosen for expedience. A design that silently assumes one machine, or that declares "machine-local BY DESIGN" without naming that concrete reason, is a violation, not a valid posture.
467
+ **Rule.** Every feature, state surface, and design decision treats the agent as a SINGLE entity spread across many machines, BY DEFAULT. "Unified across my machines" is the default posture; "machine-local" is an EXCEPTION that must be justified with a concrete reason it *cannot* be unified (a login or credential that physically lives in one disk's keychain; a hardware-bound resource; an operator-ratified exception) — never a default chosen for expedience. A design that silently assumes one machine, or that declares "machine-local BY DESIGN" without naming that concrete reason, is a violation, not a valid posture. **Single-machine survivability (ratified 2026-08-22, operator directive, topic 52222 — Amendment 1).** An agent must be able to operate AS IS on ANY SINGLE machine it is installed on. If every other machine becomes unavailable the agent loses RESOURCES — throughput, parallelism, hardware it does not have — but never INFORMATION. A design in which some information is reachable only by contacting another machine violates this, **however reliable that other machine is assumed to be**. **The subject, stated in the Rule because it decides a live collision (added at ratification, 2026-08-22).** The conservation is over what the agent KNOWS — its memory and the state a coherent reply depends on — not over per-machine OPERATIONAL records whose own articles bound them on purpose. *Decision Provenance & Outcome Review* deliberately keeps judgment-call context machine-local-full and retention-bounded, and *Observable Intelligence* requires audit trails to age out; without this boundary, survivability and those articles would each be citable against the other. The same test applies as in *Archiving May Never Mean Deleting*: losing it costs the agent its CONTINUITY, not merely a diagnostic. A machine-local operational log is therefore not a survivability violation; a machine-local piece of the agent's memory is. **And the posture this retires, named rather than left for a reader to notice (external adversarial lens, 2026-08-22).** `proxied-on-read` — a named remote read — is EXACTLY "information reachable only by contacting another machine", so for memory-bearing state it is no longer a valid destination posture: it survives only for OPERATIONAL state under the boundary above, and for memory it is at best a `migrating-to-unified` waypoint. Leaving that unsaid would have left the article enumerating a posture its own Rule forbids, citable both ways. This is strictly stronger than the reachability test the article applied before it: a fetch-on-demand design passes "can this be unified?" (every machine *can* reach the data) while failing this one, because reachability is conditional on a peer being up. That gap is not hypothetical — it is the one the conversation-history design fell into, surviving six convergence rounds before the operator caught it on read. **The rule is an OUTCOME, not a storage mandate (ratified 2026-08-22, same directive — Amendment 2).** It requires that any machine can act coherently on anything the agent knows; it does NOT prescribe that every byte sits on every disk. Full replication, sealed per-machine replication, and replicate-index-plus-encrypted-content all satisfy it; fetch-on-demand from a live peer does not. Mechanisms may improve without a constitutional edit; the outcome may not quietly weaken. A storage mandate written into a constitution freezes today's implementation into the document and makes adopting the next better mechanism an amendment.
457
468
  **Tree placement.** **ROOT / FOUNDATIONAL** — this standard declares no parent, and that is a stated position rather than an omission. It now carries merged subsections beneath it, which is exactly when its own placement had to be settled rather than assumed. Declared 2026-08-13 under the insertion-placement rule: a standard that is edited must say where it sits, because editing it is when its place in the tree is actually in mind. If a genuine parent is later identified, this becomes a child declaration — a root position is a claim that can be revised, not a permanent exemption.
458
469
  **Merged subsections.** 2 standard(s) were merged under this one on 2026-08-13 and remain LIVE and binding, each as a named subsection: **Cross-Machine Coherence** — It requires the one-agent cross-machine machinery itself to remain single-authority, internet-capable, deduplicated, and convergent under partitions and load; **Ownership-Gated Side Effects** — It requires every topic-scoped create, revive, rebind, terminate, or other side effect to prove current ownership at fire time and to fail loudly on unresolved ownership. They are named here rather than summarised into this article deliberately — the operator's condition is that the development process still encounters **every specific tripwire** each one carried, and a parent stated at this altitude cannot carry them. Removing any enforcement structure that made a subsection merge-able remains a **breach**; the merge never licenses it.
459
470
  **Fails.** If the cross-machine posture lint or semantic reviewer is unavailable, fail open and loud: ordinary work may proceed, but record and surface the missed posture review; a proposed state-fragmenting exception still fails closed.
460
471
  **Distinct from Cross-Machine Coherence.** Its sibling above governs the *robustness of the lease / seamlessness layer* under degraded conditions — the machinery that keeps N machines from becoming two agents. This one governs the *default posture of every new feature*: that machinery only makes the agent whole if features actually ride it. The per-feature posture check (2026-06-12 widening, above) already exists — but it verifies a feature *declares* a posture and accepts "machine-local BY DESIGN" as a valid declared answer, so a wrong posture (machine-local where unified was correct) passes the check by being *declared*. The gap this closes: the check tested for a declaration, not for the unified DEFAULT.
461
- **In practice.** This is **THE SINGLE GOVERNING ARTICLE for the cross-machine posture declaration** (declared 2026-08-08, fourth pass, after the review found both this article and *Cross-Machine Coherence* owning it: "both still own the declaration/review obligation"). It governs here because it STRENGTHENS the permitted answers — the enumerated postures below — while the other article owns the broader robustness rule that a posture serves. Every new state surface asserts its cross-machine posture — `unified` (rides the One Memory / stateSync machinery), `proxied-on-read` (a named remote read), or `machine-local + justification` (the reason jailed to a closed taxonomy: physical-credential-locality, hardware-bound-resource, or an operator-ratified exception). Absence defaults to `unified`-required. A bare "machine-local BY DESIGN" with no justification fails review — the default is *asserted*, never *assumed*.
472
+ **In practice.** This is **THE SINGLE GOVERNING ARTICLE for the cross-machine posture declaration** (declared 2026-08-08, fourth pass, after the review found both this article and *Cross-Machine Coherence* owning it: "both still own the declaration/review obligation"). It governs here because it STRENGTHENS the permitted answers — the enumerated postures below — while the other article owns the broader robustness rule that a posture serves. Every new state surface asserts its cross-machine posture — `unified` (rides the One Memory / stateSync machinery), `proxied-on-read` (a named remote read — **OPERATIONAL state ONLY; invalid for memory-bearing state**, see the survivability clause in the Rule), or `machine-local + justification` (the reason jailed to the closed taxonomy stated above: physical-credential-locality [narrowed 2026-08-22], hardware-bound-resource, operator-ratified-exception, or migrating-to-unified [added 2026-08-22]). Absence defaults to `unified`-required. A bare "machine-local BY DESIGN" with no justification fails review — the default is *asserted*, never *assumed*. **THE CLOSED TAXONOMY, amended 2026-08-22 (operator directive, topic 52222 — Amendments 3 and 5).** A `machine-local` posture jails its reason to this closed set, and widening the set remains a constitution-bound operator decision, never an author's convenience. **`physical-credential-locality` — NARROWED (Amendment 3).** It covers only a credential whose relocation is PROHIBITED or technically impossible: a vendor's terms of service, a hardware-bound key, a legal residency constraint. It does NOT cover a credential that merely HAPPENS to be stored on one disk. Where a vault (Bitwarden and equivalents) can hold it, the locality is a storage choice and this key does not apply. A claim under this key must NAME its basis — the prohibiting authority (`prohibited-by`) or, where the barrier is technical rather than forbidding, what makes relocation impossible (`impossible-because`). Both are accepted, because "technically impossible" often has no authority to cite and demanding one was an inconsistent evidentiary bar (external adversarial lens, 2026-08-22). The claim must also declare whether the barrier is expected to be PERMANENT or TEMPORARY, and **a TEMPORARY declaration must record its exit** (`exit=<ref>`), **a re-review date** (`expires=YYYY-MM-DD`, at most 180 days out), **and the date it was first declared** (`since=YYYY-MM-DD`, with the same 360-day TOTAL-LIFETIME cap) — the full contract `migrating-to-unified` carries, for the same reason and closed in the same place (round 4: giving this key a deadline without a lifetime cap re-created, one key over, exactly the renew-every-180-days loophole round 3 had just closed for the other; a fix that reintroduces the defect it was patterned on is not a fix). At the cap the barrier is re-argued in front of the operator, who may of course rule it PERMANENT — which is the honest outcome for a barrier that has held for a year: a barrier declared temporary with no tracked way out is a permanent barrier wearing a temporary label, and one with an exit but no deadline is the same thing with paperwork (the deadline was added in round 3 after an external lens observed the exit ref alone let a TEMPORARY barrier outlive the horizon imposed on the newer key — a rule that binds a new key more tightly than an old one for no stated reason is an inconsistency, not a conservative default). The one genuine current instance is Anthropic's prohibition on relocating a Claude login between machines (why WS5.2 re-mints per machine rather than copying a token); under this amendment it still passes, and must be declared TEMPORARY with the planned exit recorded — API-key doorways once per-machine login becomes untenable at scale (operator, same directive). **`hardware-bound-resource`** — unchanged. **`operator-ratified-exception`** — unchanged; still requires a machine-verifiable, existence-checkable ref. **`migrating-to-unified` — NEW (Amendment 5).** Permitted ONLY when the marker also cites (a) the ratified decision establishing `unified` as the destination and (b) a resolvable tracking ref for the work that delivers it, AND carries an expiry date, and that date may be **at most 180 days out** — a bound added at ratification after an external adversarial lens showed the key was RENEWABLE rather than self-terminating (an author could set a distant expiry, deliver nothing, and never be asked again; the first draft's own test fixture used the year 2099 and passed). On expiry the surface either declares `unified` or the exception is re-argued in front of the operator; it may never lapse silently into a permanent posture. **And because a per-declaration horizon alone is merely RENEWABLE (round 3, external lens: "renewal forever remains possible; assigning that loophole to semantic review does not close it"), the marker also carries `since=YYYY-MM-DD` — the date the posture was FIRST declared for that surface — and the posture's TOTAL LIFETIME (`expires` − `since`) may not exceed 360 days.** A renewal must carry the original `since` forward, so re-dating the expiry no longer buys unbounded time: the second renewal is the last one the parser will accept. **What remains open, stated rather than claimed closed:** a `since` that is simply rewritten is FALSIFICATION, not a loophole in the letter — the parser cannot detect it, and nothing here pretends otherwise. The difference matters: an author must now lie to keep the posture, where before they only had to pick a distant date. **Why a fourth key was ratified despite the hatch being deliberately narrow.** Amendment 1 GUARANTEES arrivals at this wall: making survivability the test converts a population of currently-passing `machine-local` surfaces into surfaces whose correct posture is `unified` and whose mechanism is unbuilt. With only three keys the honest posture is unavailable — the conversation-history spec keyed nine surfaces `operator-ratified-exception (PENDING)` for a ratification the operator then DENIED, leaving markers citing a refused approval. Adopting 1 without 5 therefore MANUFACTURES the false-claim class the taxonomy exists to reduce. This key is the only one of the four whose use is self-terminating — it requires a date and a tracked delivery, so it cannot become a resting place — and unlike Amendments 1, 2 and 4 its contract is deterministically CHECKABLE on arrival, because its citation requirements reduce to the same existence-check the marker lint already performs. **The honest scope of that check:** the parser verifies the marker's SHAPE — that an authority is named, that refs are ref-shaped, that a date is present and unexpired and inside the maximum horizon. It cannot verify that the named authority actually prohibits the relocation, that the tracked work will deliver unification, or that an expiry triggers real re-argument. Those remain the `/spec-converge` reviewer's semantic authority, exactly as before; "checkable" here never means "proven true".
462
473
  **Earned from.** 2026-07-03 (topic 29723), operator-ratified: the tiered-intelligence-delegation spec defaulted its consult memory to machine-local, conflicting with the single-unified-agent goal — and it survived SEVEN convergence rounds before the operator caught it on read. That it took the operator's read, not the review machinery, is the tell: the always-multi-machine expectation lived in prose and reviewer habit, not in structure. Justin named the meta-point — "the fact that I had to point out the misalignment means we have a gap in our standards or how we enforce them; it should be fundamental in our constitution that an Instar agent is always supported as a multi-machine entity."
463
474
  **Traces to the goal.** The founding promise is one coherent, persistent agent. An agent whose memory, budgets, or state fragment per-machine is not one agent — it is N confused copies of it. *Structure beats Willpower*: don't rely on the agent or a reviewer *remembering* that the agent is multi-machine; make the unified default the path of least resistance and machine-local the posture that must argue for itself.
464
- **Applied through.** The deterministic marker floor `scripts/lint-machine-local-justification.js` — a no-LLM static parser that grades the PRESENCE + well-formedness of the `machine-local-justification: <taxonomy-key>` marker a spec must carry per machine-local surface: an undefended machine-local posture fails, and — bidirectionally — a spurious/out-of-taxonomy marker or an `operator-ratified-exception` citing no machine-verifiable ref fails too (self-tested with positive, undefended, and spurious-marker fixtures). This is the cheap deterministic SIGNAL; the `/spec-converge` integration reviewer holds the semantic AUTHORITY (is the justification actually TRUE?) the parser cannot make. The lint ships REPORT-FIRST (a non-blocking signal; `--strict` is the FAIL capability) per the honesty / hard-sequencing clause of `docs/specs/three-standards-enforcement.md` (§197-202, §563-573) and the dark-first Maturation convention. The `/spec-converge` cross-machine check is also STRENGTHENED to REJECT "machine-local" unless the spec carries an explicit `machine-local-justification` (a bare "machine-local BY DESIGN" fails); the side-effects review §7 posture field gains the same justification requirement; and existing features are swept for undefended machine-local surfaces (folds into the feature-maturation audit, topic 30668). This registry entry + its **P21** twin in `docs/INSTAR-DESIGN-PRINCIPLES-AND-LESSONS.md` fire the `/spec-converge` lessons-aware reviewer on every future spec.
475
+ **Applied through.** The deterministic marker floor `scripts/lint-machine-local-justification.js` — a no-LLM static parser that grades the PRESENCE + well-formedness of the `machine-local-justification: <taxonomy-key>` marker a spec must carry per machine-local surface: an undefended machine-local posture fails, and — bidirectionally — a spurious/out-of-taxonomy marker or an `operator-ratified-exception` citing no machine-verifiable ref fails too (self-tested with positive, undefended, and spurious-marker fixtures). This is the cheap deterministic SIGNAL; the `/spec-converge` integration reviewer holds the semantic AUTHORITY (is the justification actually TRUE?) the parser cannot make. The lint ships REPORT-FIRST (a non-blocking signal; `--strict` is the FAIL capability) per the honesty / hard-sequencing clause of `docs/specs/three-standards-enforcement.md` (§197-202, §563-573) and the dark-first Maturation convention. The `/spec-converge` cross-machine check is also STRENGTHENED to REJECT "machine-local" unless the spec carries an explicit `machine-local-justification` (a bare "machine-local BY DESIGN" fails); the side-effects review §7 posture field gains the same justification requirement; and existing features are swept for undefended machine-local surfaces (folds into the feature-maturation audit, topic 30668). This registry entry + its **P21** twin in `docs/INSTAR-DESIGN-PRINCIPLES-AND-LESSONS.md` fire the `/spec-converge` lessons-aware reviewer on every future spec. **UNENFORCED SUB-OBLIGATION, named (2026-08-22).** Amendments 1 and 2 have NO deterministic guard and are not claimed to have one: no parser can decide whether a design survives the loss of every peer, nor whether a stated outcome is genuinely met. They land as `/spec-converge` cross-machine reviewer questions — "does this survive every peer disappearing?" — which is semantic authority, weaker than a lint and stronger than prose. Amendments 3 and 5 carry deterministically CHECKABLE MARKER CONTRACTS, enforced in `scripts/lint-machine-local-justification.js`. What is enforced is the marker's SHAPE — a basis is named, refs are ref-shaped, dates are present, unexpired, and inside their caps. The SEMANTIC facts those markers assert — that the named authority really prohibits relocation, that the impossibility is real, that the tracked work delivers unification — are NOT checked by anything deterministic and remain the reviewer's (re-narrowed 2026-08-22 round 3, after an external lens read the flat word "enforced" as covering the semantics). Naming this asymmetry rather than citing a guard for the unguarded clauses is deliberate: *Cross-Store Coherence* records that naming a guard in prose made an article classify as ENFORCED by a guard measuring something else, and reproducing that inside the article being amended to be more honest would be the worst available outcome. **Sub-obligation countdown.** `2026-09-22` — tracked as `STD-SUBCOUNTDOWN-multi-machine-survivability`.
465
476
 
466
477
  ### Cross-Store Coherence Is an Invariant
467
478
  **Rule.** Any two stores that answer the same question — about identity, authority, machine liveness, or configuration — must have a **declared agreement invariant**, and that invariant must be **checked on a cadence by machinery**. A pair of authoritative stores with no coherence check is a contradiction waiting for a code path to read the wrong one. When a NEW store is introduced that answers a question an existing store already answers, declaring the invariant is part of introducing it.
@@ -1,6 +1,6 @@
1
1
  {
2
- "sha256": "81b53363a440e832672618965540b3e507ae0d93adcc67ec2b93daf7933b3ab4",
3
- "articleCount": 88,
2
+ "sha256": "5e49a5f5481c67c725c3bab997ab085370c5904e92e1ebf480d6105711e1e305",
3
+ "articleCount": 89,
4
4
  "generatedFrom": "docs/STANDARDS-REGISTRY.md",
5
- "packageVersion": "1.3.1189"
5
+ "packageVersion": "1.3.1191"
6
6
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instar",
3
- "version": "1.3.1189",
3
+ "version": "1.3.1191",
4
4
  "description": "Coherence infrastructure for self-evolving AI agents — on the Claude Code or Codex subscription you already have.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -70,6 +70,11 @@ export const TAXONOMY_KEYS = new Set([
70
70
  'physical-credential-locality',
71
71
  'hardware-bound-resource',
72
72
  'operator-ratified-exception',
73
+ // Ratified 2026-08-22 (operator directive, topic 52222 — Amendment 5). The
74
+ // ONLY self-terminating key: it carries a first-declared date, a bounded
75
+ // re-review horizon, and a total-lifetime cap, so it cannot become a resting
76
+ // place. See the A2-migrating-* contracts below.
77
+ 'migrating-to-unified',
73
78
  ]);
74
79
 
75
80
  // ── Finding the posture section (widened 2026-08-21) ──────────────────────
@@ -188,11 +193,269 @@ export function hasResolvableRef(rest) {
188
193
  return REF_SHA_RE.test(rest) || REF_URL_RE.test(rest) || REF_REGISTRY_KEY_RE.test(rest);
189
194
  }
190
195
 
196
+ /**
197
+ * Parse `name=value` / `name: value` sub-fields out of a marker's `rest`.
198
+ *
199
+ * Amendments 3 and 5 (ratified 2026-08-22) require a marker to carry NAMED
200
+ * facts rather than free prose — that is what makes them deterministically
201
+ * checkable rather than a reviewer's judgement. A value runs to the next known
202
+ * field name or to end-of-line, and may be quoted.
203
+ *
204
+ * Returns a lowercase-keyed object; a repeated field keeps the FIRST value, so
205
+ * appending a second `permanence=` cannot quietly override the first.
206
+ */
207
+ export const MARKER_FIELDS = ['prohibited-by', 'impossible-because', 'permanence', 'exit', 'ratified', 'tracking', 'since', 'expires'];
208
+ export function parseMarkerFields(rest) {
209
+ const out = {};
210
+ if (!rest) return out;
211
+ const names = MARKER_FIELDS.map((n) => n.replace(/[-]/g, '\\-')).join('|');
212
+ const re = new RegExp(
213
+ `\\b(${names})\\s*[=:]\\s*(?:"([^"]*)"|'([^']*)'|([^\\s].*?))(?=\\s+(?:${names})\\s*[=:]|\\s*$)`,
214
+ 'gi',
215
+ );
216
+ let m;
217
+ while ((m = re.exec(rest)) !== null) {
218
+ const key = m[1].toLowerCase();
219
+ const val = (m[2] ?? m[3] ?? m[4] ?? '').trim();
220
+ if (!(key in out)) out[key] = val;
221
+ }
222
+ return out;
223
+ }
224
+
225
+ const ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
226
+
227
+ // Amendment 5's maximum horizon. Without it the key is RENEWABLE, not
228
+ // self-terminating: an author sets a distant expiry, delivers nothing, and is
229
+ // never asked again. The first draft's own fixture used the year 2099 and
230
+ // passed, which is how the gap was found (external adversarial lens, 2026-08-22).
231
+ export const MIGRATION_MAX_HORIZON_DAYS = 180;
232
+
233
+ // Round 3. The horizon alone bounds ONE declaration; renewing it re-dates the
234
+ // expiry and buys unbounded time. `since` (first declaration) plus a TOTAL
235
+ // LIFETIME cap is what makes the key actually terminate. A rewritten `since` is
236
+ // falsification, not a loophole — the parser cannot see it, and the article says so.
237
+ export const MIGRATION_MAX_TOTAL_LIFETIME_DAYS = 360;
238
+
239
+ /** Shared: an ISO date field that must be present, well-formed, and within `maxDays` of today. */
240
+ function gradeHorizonDate(value, { rule, label, maxDays, today, missingMessage }) {
241
+ const findings = [];
242
+ if (!ISO_DATE_RE.test(value || '') || Number.isNaN(Date.parse(value))) {
243
+ findings.push({ rule: `${rule}-missing`, message: missingMessage });
244
+ return { findings, dueMs: null };
245
+ }
246
+ const dueMs = Date.parse(`${value}T23:59:59Z`);
247
+ if (dueMs > today.getTime() + maxDays * 24 * 60 * 60 * 1000) {
248
+ findings.push({
249
+ rule: `${rule}-beyond-horizon`,
250
+ message:
251
+ `${label} ${value} is more than ${maxDays} days out. A distant date makes the ` +
252
+ `posture renewable rather than self-terminating — it must come back in front of ` +
253
+ `a reader on a bounded cadence.`,
254
+ });
255
+ } else if (dueMs < today.getTime()) {
256
+ findings.push({ rule: `${rule}-expired`, message: `${label} ${value} has PASSED. The posture must be re-argued or resolved; it may never lapse silently into a permanent one.` });
257
+ }
258
+ return { findings, dueMs };
259
+ }
260
+
261
+ /**
262
+ * Amendment 3 — `physical-credential-locality` must NAME the prohibiting
263
+ * authority and declare the prohibition PERMANENT or TEMPORARY. Returns a
264
+ * findings array (empty when well-formed).
265
+ *
266
+ * Why named fields rather than prose: the key as previously written let an
267
+ * author assert a physical constraint where a storage habit existed, and no
268
+ * parser could tell the two apart. "Bitwarden could hold this" is the test, and
269
+ * a marker that cannot even name who forbids the move has not met it.
270
+ */
271
+ export function gradePhysicalCredentialLocality(rest, today = new Date()) {
272
+ const findings = [];
273
+ const f = parseMarkerFields(rest);
274
+ // The basis may be a PROHIBITION or an IMPOSSIBILITY. Demanding a
275
+ // `prohibited-by` for a purely technical barrier was an inconsistent
276
+ // evidentiary bar — a hardware-bound key has no authority to cite (external
277
+ // adversarial lens, 2026-08-22).
278
+ if (!f['prohibited-by'] && !f['impossible-because']) {
279
+ findings.push({
280
+ rule: 'A2-credential-locality-no-authority',
281
+ message:
282
+ `physical-credential-locality must NAME its basis — either ` +
283
+ `\`prohibited-by="<vendor ToS | legal residency constraint>"\` or ` +
284
+ `\`impossible-because="<what makes relocation technically impossible>"\` ` +
285
+ `(Amendment 3, ratified 2026-08-22). This key covers only a credential whose ` +
286
+ `relocation is PROHIBITED or technically impossible; a credential that merely ` +
287
+ `HAPPENS to sit on one disk is a storage choice, and where a vault can hold it ` +
288
+ `this key does not apply. Marker value: "${rest || '(empty)'}".`,
289
+ });
290
+ }
291
+ const perm = (f.permanence || '').toLowerCase();
292
+ // A TEMPORARY barrier with no tracked way out is a permanent barrier wearing a
293
+ // temporary label — the defect `migrating-to-unified` carries a horizon against,
294
+ // and it applied here identically (external adversarial lens, 2026-08-22).
295
+ if (perm === 'temporary') {
296
+ if (!hasResolvableRef(f.exit || '')) {
297
+ findings.push({
298
+ rule: 'A2-credential-locality-temporary-no-exit',
299
+ message:
300
+ `physical-credential-locality declared \`permanence=temporary\` must record its ` +
301
+ `exit — \`exit=<commit SHA | URL | dotted registry key>\` naming the work or ` +
302
+ `decision that ends the barrier (Amendment 3, ratified 2026-08-22). Marker value: ` +
303
+ `"${rest || '(empty)'}".`,
304
+ });
305
+ }
306
+ // An exit with no deadline is a permanent barrier with paperwork. Same horizon
307
+ // the newer key carries — binding a new key more tightly than an old one for no
308
+ // stated reason is an inconsistency, not a conservative default (round 3).
309
+ // Round 4: a deadline WITHOUT a lifetime cap is renewable every 180 days
310
+ // forever — the exact loophole round 3 closed one key over. Same contract here.
311
+ const tsince = f.since || '';
312
+ if (!ISO_DATE_RE.test(tsince) || Number.isNaN(Date.parse(tsince))) {
313
+ findings.push({
314
+ rule: 'A2-credential-locality-temporary-no-since',
315
+ message:
316
+ `physical-credential-locality declared \`permanence=temporary\` must carry ` +
317
+ `\`since=YYYY-MM-DD\` — the date the barrier was FIRST declared, carried forward ` +
318
+ `across renewals (Amendment 3, round 4). Without it the re-review date is ` +
319
+ `renewable forever. Marker value: "${rest || '(empty)'}".`,
320
+ });
321
+ } else if (ISO_DATE_RE.test(f.expires || '') && !Number.isNaN(Date.parse(f.expires))) {
322
+ const lifetimeDays = (Date.parse(f.expires) - Date.parse(tsince)) / (24 * 60 * 60 * 1000);
323
+ if (lifetimeDays > MIGRATION_MAX_TOTAL_LIFETIME_DAYS) {
324
+ findings.push({
325
+ rule: 'A2-credential-locality-temporary-lifetime-exceeded',
326
+ message:
327
+ `physical-credential-locality has been declared TEMPORARY for a TOTAL of ` +
328
+ `${Math.round(lifetimeDays)} days (since ${tsince} → expires ${f.expires}), over ` +
329
+ `the ${MIGRATION_MAX_TOTAL_LIFETIME_DAYS}-day lifetime cap. Re-argue it in front ` +
330
+ `of the operator, who may rule it PERMANENT — the honest outcome for a barrier ` +
331
+ `that has held for a year.`,
332
+ });
333
+ }
334
+ }
335
+ for (const finding of gradeHorizonDate(f.expires, {
336
+ rule: 'A2-credential-locality-temporary-review',
337
+ label: 'physical-credential-locality TEMPORARY re-review date',
338
+ maxDays: MIGRATION_MAX_HORIZON_DAYS,
339
+ today,
340
+ missingMessage:
341
+ `physical-credential-locality declared \`permanence=temporary\` must carry a ` +
342
+ `re-review date — \`expires=YYYY-MM-DD\`, at most ${MIGRATION_MAX_HORIZON_DAYS} ` +
343
+ `days out (Amendment 3, round 3). Marker value: "${rest || '(empty)'}".`,
344
+ }).findings) findings.push(finding);
345
+ }
346
+ if (perm !== 'permanent' && perm !== 'temporary') {
347
+ findings.push({
348
+ rule: 'A2-credential-locality-no-permanence',
349
+ message:
350
+ `physical-credential-locality must declare \`permanence=permanent\` or ` +
351
+ `\`permanence=temporary\` (Amendment 3, ratified 2026-08-22). A temporary ` +
352
+ `prohibition is a posture with an exit; a permanent one is a standing constraint, ` +
353
+ `and conflating them hides which is which. Marker value: "${rest || '(empty)'}".`,
354
+ });
355
+ }
356
+ return findings;
357
+ }
358
+
359
+ /**
360
+ * Amendment 5 — `migrating-to-unified` is permitted ONLY with (a) the ratified
361
+ * decision establishing unified as the destination, (b) a resolvable tracking
362
+ * ref for the work that delivers it, and (c) an expiry date. On expiry the
363
+ * surface declares `unified` or the exception is re-argued in front of the
364
+ * operator; it may never lapse silently into a permanent posture — so an
365
+ * EXPIRED marker is itself a finding, not a grace period.
366
+ *
367
+ * `today` is injectable so the expiry arm is testable without freezing a clock.
368
+ */
369
+ export function gradeMigratingToUnified(rest, today = new Date()) {
370
+ const findings = [];
371
+ const f = parseMarkerFields(rest);
372
+ if (!hasResolvableRef(f.ratified || '')) {
373
+ findings.push({
374
+ rule: 'A2-migrating-no-ratified-decision',
375
+ message:
376
+ `migrating-to-unified must cite the ratified decision establishing \`unified\` as the ` +
377
+ `destination — \`ratified=<commit SHA | URL | dotted registry key>\` (Amendment 5, ` +
378
+ `ratified 2026-08-22). Without it the key asserts a destination nobody approved, which ` +
379
+ `is the false-claim class it exists to prevent. Marker value: "${rest || '(empty)'}".`,
380
+ });
381
+ }
382
+ if (!hasResolvableRef(f.tracking || '')) {
383
+ findings.push({
384
+ rule: 'A2-migrating-no-tracking-ref',
385
+ message:
386
+ `migrating-to-unified must cite a resolvable tracking ref for the work that DELIVERS ` +
387
+ `unified — \`tracking=<commit SHA | URL | dotted registry key>\` (Amendment 5). A ` +
388
+ `migration with no tracked delivery is a permanent posture wearing a temporary label. ` +
389
+ `Marker value: "${rest || '(empty)'}".`,
390
+ });
391
+ }
392
+ // Round 3: the total lifetime, not just this declaration's horizon.
393
+ const since = f.since || '';
394
+ if (!ISO_DATE_RE.test(since) || Number.isNaN(Date.parse(since))) {
395
+ findings.push({
396
+ rule: 'A2-migrating-no-since',
397
+ message:
398
+ `migrating-to-unified must carry \`since=YYYY-MM-DD\` — the date the posture was ` +
399
+ `FIRST declared for this surface, carried forward unchanged across renewals ` +
400
+ `(Amendment 5, round 3). Without it the horizon bounds one declaration and a ` +
401
+ `renewal buys unbounded time. Marker value: "${rest || '(empty)'}".`,
402
+ });
403
+ }
404
+ const expires = f.expires || '';
405
+ if (ISO_DATE_RE.test(since) && ISO_DATE_RE.test(expires) &&
406
+ !Number.isNaN(Date.parse(since)) && !Number.isNaN(Date.parse(expires))) {
407
+ const lifetimeDays = (Date.parse(expires) - Date.parse(since)) / (24 * 60 * 60 * 1000);
408
+ if (lifetimeDays > MIGRATION_MAX_TOTAL_LIFETIME_DAYS) {
409
+ findings.push({
410
+ rule: 'A2-migrating-lifetime-exceeded',
411
+ message:
412
+ `migrating-to-unified has been in force for a TOTAL of ${Math.round(lifetimeDays)} ` +
413
+ `days (since ${since} → expires ${expires}), over the ` +
414
+ `${MIGRATION_MAX_TOTAL_LIFETIME_DAYS}-day lifetime cap. The surface must declare ` +
415
+ `\`unified\` or the exception must be re-argued in front of the operator — a ` +
416
+ `renewal may not carry it further.`,
417
+ });
418
+ }
419
+ }
420
+ if (!ISO_DATE_RE.test(expires) || Number.isNaN(Date.parse(expires))) {
421
+ findings.push({
422
+ rule: 'A2-migrating-no-expiry',
423
+ message:
424
+ `migrating-to-unified must carry \`expires=YYYY-MM-DD\` (Amendment 5). The expiry is ` +
425
+ `what makes this key self-terminating and is the entire reason a fourth key was ` +
426
+ `ratified into a deliberately narrow taxonomy. Marker value: "${rest || '(empty)'}".`,
427
+ });
428
+ } else {
429
+ const due = Date.parse(`${expires}T23:59:59Z`);
430
+ const horizonMs = MIGRATION_MAX_HORIZON_DAYS * 24 * 60 * 60 * 1000;
431
+ if (due > today.getTime() + horizonMs) {
432
+ findings.push({
433
+ rule: 'A2-migrating-expiry-beyond-horizon',
434
+ message:
435
+ `migrating-to-unified expiry ${expires} is more than ` +
436
+ `${MIGRATION_MAX_HORIZON_DAYS} days out (Amendment 5's maximum horizon). A ` +
437
+ `distant expiry makes the key renewable rather than self-terminating — the ` +
438
+ `posture must come back in front of a reader on a bounded cadence.`,
439
+ });
440
+ }
441
+ if (due < today.getTime()) {
442
+ findings.push({
443
+ rule: 'A2-migrating-expired',
444
+ message:
445
+ `migrating-to-unified EXPIRED on ${expires}. The surface must now declare \`unified\` ` +
446
+ `or the exception must be re-argued in front of the operator; it may never lapse ` +
447
+ `silently into a permanent posture (Amendment 5).`,
448
+ });
449
+ }
450
+ }
451
+ return findings;
452
+ }
453
+
191
454
  /**
192
455
  * Grade one spec's text against Standard A's marker contract. Pure — no I/O.
193
456
  * Returns { findings: [{ rule, message }] }.
194
457
  */
195
- export function gradeMachineLocalMarkers(text) {
458
+ export function gradeMachineLocalMarkers(text, today = new Date()) {
196
459
  const findings = [];
197
460
  const markers = parseMarkers(text);
198
461
  const posture = findPostureSection(text);
@@ -209,6 +472,12 @@ export function gradeMachineLocalMarkers(text) {
209
472
  });
210
473
  continue;
211
474
  }
475
+ if (marker.key === 'physical-credential-locality') {
476
+ for (const f of gradePhysicalCredentialLocality(marker.rest, today)) findings.push(f);
477
+ }
478
+ if (marker.key === 'migrating-to-unified') {
479
+ for (const f of gradeMigratingToUnified(marker.rest, today)) findings.push(f);
480
+ }
212
481
  if (marker.key === 'operator-ratified-exception' && !hasResolvableRef(marker.rest)) {
213
482
  findings.push({
214
483
  rule: 'A2-unresolvable-ratification-ref',
@@ -236,13 +505,22 @@ export function gradeMachineLocalMarkers(text) {
236
505
  if (posture) {
237
506
  const sectionText = text.slice(posture.start, posture.end);
238
507
  const assertsMachineLocal = /machine-local/i.test(sectionText);
239
- const hasValidMarker = markers.some(
240
- (mk) =>
241
- TAXONOMY_KEYS.has(mk.key) &&
242
- mk.index >= posture.start &&
243
- mk.index < posture.end &&
244
- (mk.key !== 'operator-ratified-exception' || hasResolvableRef(mk.rest)),
245
- );
508
+ // A marker DEFENDS a machine-local posture only when it is well-formed FOR ITS
509
+ // KEY. Amendments 3 and 5 added per-key contracts, so a marker carrying the
510
+ // right key and nothing else must not count as a defence — otherwise the new
511
+ // requirements would be reportable while the posture they govern still passed.
512
+ const hasValidMarker = markers.some((mk) => {
513
+ if (!TAXONOMY_KEYS.has(mk.key)) return false;
514
+ if (mk.index < posture.start || mk.index >= posture.end) return false;
515
+ if (mk.key === 'operator-ratified-exception') return hasResolvableRef(mk.rest);
516
+ if (mk.key === 'physical-credential-locality') {
517
+ return gradePhysicalCredentialLocality(mk.rest, today).length === 0;
518
+ }
519
+ if (mk.key === 'migrating-to-unified') {
520
+ return gradeMigratingToUnified(mk.rest, today).length === 0;
521
+ }
522
+ return true;
523
+ });
246
524
  if (assertsMachineLocal && !hasValidMarker) {
247
525
  findings.push({
248
526
  rule: 'A1-undefended-machine-local',
@@ -56,6 +56,7 @@ import yaml from 'js-yaml';
56
56
  import { articleIds, parseRegistryStructure } from './standards-registry-article-core.mjs';
57
57
  import {
58
58
  evaluateStandardsDirection,
59
+ evaluateOperatorReviewApproval,
59
60
  inventoryStandardsArticles,
60
61
  readCandidateApproverKey,
61
62
  readDirectionApprovalLedger,
@@ -599,6 +600,12 @@ function validateRootSelfWiring() {
599
600
  STANDARDS_DIRECTION_BASE_FILE: '${{ runner.temp }}/standards-registry-base.md',
600
601
  STANDARDS_DIRECTION_BASE_APPROVER_KEY_FILE: '${{ runner.temp }}/standards-direction-approver-base.pem',
601
602
  STANDARDS_DIRECTION_BASE_REVISION: "${{ github.event.pull_request.base.sha || github.event.before || format('{0}^', github.sha) }}",
603
+ // Path B (2026-08-22): the operator-review context the guard reads. Pinned
604
+ // here for the same reason as the others — this contract is deliberately
605
+ // EXACT so the CI wiring cannot be quietly rearranged, and adding an input
606
+ // to the check must therefore be a visible, declared edit rather than a
607
+ // silent one. The guard caught exactly that when this key was introduced.
608
+ STANDARDS_DIRECTION_REVIEW_FILE: '${{ runner.temp }}/standards-direction-review.json',
602
609
  };
603
610
  if (!exactKeys(job, ['name', 'runs-on', 'steps']) ||
604
611
  job.name !== 'Standards Enforcement Coverage' || job['runs-on'] !== 'ubuntu-latest' ||
@@ -611,6 +618,11 @@ function validateRootSelfWiring() {
611
618
  const setupStep = steps.find((step) => step?.uses === 'actions/setup-node@v4');
612
619
  const installStep = steps.find((step) => step?.run === 'npm ci --ignore-scripts');
613
620
  const baseStep = steps.find((step) => step?.id === 'area-audit-base');
621
+ // Path B's evidence-gathering step. Pinned into the ordered prefix so it
622
+ // cannot be dropped without this check failing — an absent step would make
623
+ // every operator approval read as UNAVAILABLE, which fails safe but would
624
+ // silently return the operator to needing a key.
625
+ const reviewStep = steps.find((step) => step?.name === 'Fetch operator review context (direction guard path B)');
614
626
  const expectedBaseRun = [
615
627
  'git cat-file -e "$BASE_SHA^{commit}"',
616
628
  'if git cat-file -e "$BASE_SHA:docs/standards-registry-area-audits.json"; then',
@@ -624,7 +636,7 @@ function validateRootSelfWiring() {
624
636
  '',
625
637
  ].join('\n');
626
638
  const expectedBaseSha = "${{ github.event.pull_request.base.sha || github.event.before || format('{0}^', github.sha) }}";
627
- const exactPrefix = [checkoutStep, setupStep, installStep, baseStep, checkStep];
639
+ const exactPrefix = [checkoutStep, setupStep, installStep, baseStep, reviewStep, checkStep];
628
640
  const ordered = exactPrefix.every((step, index) => step && steps[index] === step);
629
641
  const protectedBaseWired = ordered &&
630
642
  exactKeys(checkoutStep, ['uses', 'with']) && exactKeys(checkoutStep.with, ['fetch-depth']) && checkoutStep.with['fetch-depth'] === 0 &&
@@ -633,7 +645,11 @@ function validateRootSelfWiring() {
633
645
  exactKeys(baseStep, ['name', 'id', 'env', 'run']) &&
634
646
  baseStep.name === 'Resolve protected-base area ledger' &&
635
647
  exactKeys(baseStep.env, ['BASE_SHA']) && baseStep.env.BASE_SHA === expectedBaseSha &&
636
- baseStep.run === expectedBaseRun;
648
+ baseStep.run === expectedBaseRun &&
649
+ exactKeys(reviewStep, ['name', 'if', 'env', 'run']) &&
650
+ reviewStep.if === "github.event_name == 'pull_request'" &&
651
+ exactKeys(reviewStep.env, ['GH_TOKEN', 'PR', 'HEAD_SHA', 'OWNER_LOGIN', 'OWNER_TYPE', 'PR_AUTHOR', 'OUT']) &&
652
+ reviewStep.env.OUT === '${{ runner.temp }}/standards-direction-review.json';
637
653
  if (!protectedBaseWired) {
638
654
  errors.push('The Root self-wiring requires dependency install plus full-history protected-base extraction and required base env on the standards check');
639
655
  }
@@ -1317,6 +1333,31 @@ function compute() {
1317
1333
  if (key.revision !== base.revision) {
1318
1334
  key.errors.push('protected-base registry and approver trust root resolved from different revisions');
1319
1335
  }
1336
+ // PATH B context (2026-08-22). The WORKFLOW fetches the reviews and writes
1337
+ // them here, so this script performs no network I/O and stays testable.
1338
+ // Absent, unreadable or malformed => UNAVAILABLE, which falls back to
1339
+ // requiring a signed ratification. It never becomes an approval.
1340
+ const review = (() => {
1341
+ const file = process.env.STANDARDS_DIRECTION_REVIEW_FILE;
1342
+ if (!file) return null;
1343
+ let raw;
1344
+ try {
1345
+ const stat = fs.lstatSync(file);
1346
+ if (!stat.isFile() || stat.isSymbolicLink()) throw new Error('not a regular file');
1347
+ raw = JSON.parse(fs.readFileSync(file, 'utf8'));
1348
+ } catch (err) {
1349
+ return { approved: false, approvedBy: null, reason: `review context unreadable (${err.message})` };
1350
+ }
1351
+ if (!isPlainObject(raw)) return { approved: false, approvedBy: null, reason: 'review context is not an object' };
1352
+ return evaluateOperatorReviewApproval({
1353
+ reviews: raw.reviews,
1354
+ ownerLogin: raw.ownerLogin,
1355
+ ownerType: raw.ownerType,
1356
+ headSha: raw.headSha,
1357
+ prAuthorLogin: raw.prAuthorLogin,
1358
+ });
1359
+ })();
1360
+
1320
1361
  const assessed = evaluateStandardsDirection({
1321
1362
  baseMarkdown: base.markdown,
1322
1363
  candidateMarkdown: markdown,
@@ -1324,6 +1365,7 @@ function compute() {
1324
1365
  approverPublicKeyPem: key.pem,
1325
1366
  candidateApproverPublicKeyPem: candidateKey.pem,
1326
1367
  baseRevision: base.revision ?? 'unknown-protected-base',
1368
+ reviewApproval: review,
1327
1369
  });
1328
1370
  assessed.errors.unshift(...approvals.errors, ...candidateKey.errors, ...key.errors);
1329
1371
  if (assessed.errors.length > 0) assessed.status = 'not-proven';