From a77b3675320e94d14219612a1c24197ea904736f Mon Sep 17 00:00:00 2001 From: "SYM.BOT" Date: Sat, 17 Oct 2026 13:23:53 +0100 Subject: [PATCH 1/3] Remove citings of the owner from comments, docs, changelog, messages and tests State each decision and rule as a fact instead of naming who gave it. Comments, docs, the CHANGELOG, test headers and test titles lose "founder ruling", "the founder's bug report", the quoted words of the owner and one local path that held a user name; dated decisions keep their date. One string changes: the IdentityHaltError text no longer carries the owner's sentence verbatim. It states the same rule in plain words and keeps the cause and the remedy, and tests/identity-halt.test.js asserts the new wording. No code changes: the AST of every changed JS file is identical with strings masked, and the only string values that differ are that message, its assertion and one test title. Kept as vocabulary: founderAction, detectFounderAction, SYM_FOUNDER_ANCHOR, the founder key/root/anchor role, and the nodes, task owners and sources named 'founder'. --- .agents/skills/sym/SKILL.md | 2 +- .claude/skills/sym/SKILL.md | 2 +- CHANGELOG.md | 16 ++++++++-------- docs/DESIGN-room-join-authorization.md | 4 ++-- lib/config.js | 10 +++++----- lib/core/cmb-encoder.js | 2 +- lib/core/cmb-signing.js | 2 +- lib/core/mood-frame.js | 4 ++-- lib/core/record-shape.js | 10 +++++----- lib/core/room-grant.js | 2 +- lib/frame-handler.js | 2 +- lib/interior.js | 2 +- lib/node.js | 8 ++++---- lib/room-ownership.js | 2 +- tests/core-secure-final-review.test.js | 7 +++---- tests/delivery-ledger.test.js | 2 +- tests/directed-delivery-accountability.test.js | 2 +- tests/durable-inbox.test.js | 2 +- tests/identity-halt.test.js | 12 ++++++------ tests/identity-migration-by-id.test.js | 4 ++-- tests/inbox-holder-accountability.test.js | 5 ++--- tests/p6-legacy-grandfather.test.js | 2 +- tests/product-name.test.js | 4 ++-- tests/remember-targeted.test.js | 4 ++-- tests/room-join-auth.test.js | 4 ++-- tests/spec-update-attest.test.js | 2 +- tests/spec-update-frames.test.js | 4 ++-- tests/spec-update-remix-tether.test.js | 2 +- tests/spec-update-session.test.js | 2 +- tests/wire-fromname.test.js | 2 +- 30 files changed, 63 insertions(+), 65 deletions(-) diff --git a/.agents/skills/sym/SKILL.md b/.agents/skills/sym/SKILL.md index 93cd5043..e147e10c 100644 --- a/.agents/skills/sym/SKILL.md +++ b/.agents/skills/sym/SKILL.md @@ -154,7 +154,7 @@ Every change you make to SYM.BOT code MUST be production quality: - **Fix what you find.** If you spot design issues, missing spec comments, or messy logic — fix them in the same change. No "TODO" or "FIXME" left behind. - **Spec compliance.** Code must match the MMP spec. Reference section numbers in comments where relevant. - **Clean architecture.** Each module has one responsibility. Proper error handling — not swallowed. -- **No tech debt.** The founder is solo. Sloppy code today is a blocker tomorrow. The protocol is the product — the implementation must be as rigorous as the spec. +- **No tech debt.** Sloppy code today is a blocker tomorrow, and the project has no spare capacity to pay it back. The protocol is the product — the implementation must be as rigorous as the spec. ## Behaviour diff --git a/.claude/skills/sym/SKILL.md b/.claude/skills/sym/SKILL.md index 93cd5043..e147e10c 100644 --- a/.claude/skills/sym/SKILL.md +++ b/.claude/skills/sym/SKILL.md @@ -154,7 +154,7 @@ Every change you make to SYM.BOT code MUST be production quality: - **Fix what you find.** If you spot design issues, missing spec comments, or messy logic — fix them in the same change. No "TODO" or "FIXME" left behind. - **Spec compliance.** Code must match the MMP spec. Reference section numbers in comments where relevant. - **Clean architecture.** Each module has one responsibility. Proper error handling — not swallowed. -- **No tech debt.** The founder is solo. Sloppy code today is a blocker tomorrow. The protocol is the product — the implementation must be as rigorous as the spec. +- **No tech debt.** Sloppy code today is a blocker tomorrow, and the project has no spare capacity to pay it back. The protocol is the product — the implementation must be as rigorous as the spec. ## Behaviour diff --git a/CHANGELOG.md b/CHANGELOG.md index 68c5b475..e8ac97dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -632,7 +632,7 @@ carried (cutoffs, ratification, budgets per delegation subtree, `role-chain-fetc ### MMP 2.0 update 1: the drafts this release implements, as the spec now states them -The founder ruled that the drafts sym 0.14.0 implements land in the published spec before it ships. +The drafts sym 0.14.0 implements land in the published spec before it ships. They are folded into one update, MMP 2.0 update 1 (sym-bot/meshcognition-website PR #43, merged to main at c07ff20: #24, #25, #17, #22, #26, #30, #23, #31, #37, #34, #35, #27 and #28 on the §6.6 errata), and sym is aligned with its text, its reference construction (scripts/mmp/lib.mjs) and its schemas and vectors, vendored in tests/fixtures with their @@ -653,7 +653,7 @@ sym-attest-v1 pass case by case, and every frame sym builds validates against th `detail` names this node's session or a probe ping is outstanding, while no newer session is waiting to supersede, and no faster than the retry backoff (fifty forged 1011s with no detail bought three re-handshakes in five seconds; now none). A sealed ping is answered with a pong. -- **The mood frame (founder ruling).** `{ type, mood, context, timestamp }`: mood 1 to 1,024 +- **The mood frame.** `{ type, mood, context, timestamp }`: mood 1 to 1,024 characters, context at most 4,096 or null; no `from` or `fromName` (the receiver labelled the event with the payload's name); a frame carrying them is refused. `broadcastMood` throws `EMOODFRAME` rather than send one out of bounds. @@ -720,7 +720,7 @@ sym-attest-v1 pass case by case, and every frame sym builds validates against th ### Breaking: `remix()` is asynchronous, and §15.8 runs on what it produces -By the founder's ruling, §15.8 is enforced where the spec puts it, on the remixing node, in 0.14.0. +§15.8 is enforced where the spec puts it, on the remixing node, in 0.14.0. `node.remix(categories, opts)` now evaluates the remix against its nearest verified lineage root (content-only, one kernel, α-weighted drift against Tguarded) before it is minted: a faithful remix keeps its lineage; one drifted past the floor is minted as a fresh root, with no parent from the @@ -774,8 +774,8 @@ key (below), whose records are quarantined. Upgrade every node of a room togethe peer sent it, under the key that session proved (the frame is sealed on the session); it is not a signed record. Nothing about it can be verified again later, relayed with its author's signature, or attributed to anyone but that peer. A host shows it as the peer's word, not as a verified record. - The mood frame is `{ type, mood, context, timestamp }` (MMP 2.0 update 1, by the founder's - ruling): it names no sender, and `mood-delivered.from` is the session's proven name. + The mood frame is `{ type, mood, context, timestamp }` (MMP 2.0 update 1): it names no + sender, and `mood-delivered.from` is the session's proven name. ### The loaded version, and hot-swap @@ -788,7 +788,7 @@ key (below), whose records are quarantined. Upgrade every node of a room togethe own, redelivers nothing the peer re-pushes, keeps every earlier id fetching its record, and logs no null dereference (on 0.13 this broke as "Cannot read properties of null (reading 'close')"). -### Redelivery and inbox ids (founder's bug report, 2026-10) +### Redelivery and inbox ids (bug report, 2026-10) - **A delivery is surfaced once for the life of the store.** After a hot-swap (a new SymNode for the same identity, as mesh-channel's `sym_join_room` builds) or a reconnect, records already received @@ -812,7 +812,7 @@ key (below), whose records are quarantined. Upgrade every node of a room togethe ### The final re-review of f0d936a (BLOCK) — what changed -The founder ruled three design changes. Rulings A and B (what grants are kept, and what a revoked +Three design changes were settled (rulings A, B and C). A and B (what grants are kept, and what a revoked key may still sign) are met by MMP §6.6 itself (above): authority is a function of the statements held, and no statement carries a time that could place it inside a period of trust. @@ -2350,7 +2350,7 @@ disconnected. ### Changed -- **Room names must be canonical** (founder ruling 2026-08-27): one name per +- **Room names must be canonical** (decision 2026-08-27): one name per room, one room per name. `isValidRoom` now requires the round trip through the service type to be identity, so **`sym` is refused**. It satisfies the kebab grammar, maps to `_sym._tcp`, and comes back as `default` — a second diff --git a/docs/DESIGN-room-join-authorization.md b/docs/DESIGN-room-join-authorization.md index 6643aecf..311bc5b7 100644 --- a/docs/DESIGN-room-join-authorization.md +++ b/docs/DESIGN-room-join-authorization.md @@ -16,8 +16,8 @@ > key, a gated room admits only its owner. Nothing in the estate gates a room today — no owner pins > on any node — so this closed a hole rather than breaking a user. -**Date:** 2026-08-26 · **Author:** dev1 (sym runtime lane) · **Status:** v2 — mesh review SHIP-WITH-FIXES folded (x-review cmb-3e58586f…, 12 findings, all applied); FOUNDER RULED 2026-08-26: (1) grammar fix NOW, ahead of the gate; (2) ADOPT B AS FOLDED (owner-only, 24h lifetime, join-time expiry, handshake enforcement, default+sym public). Implementation = ordinary mesh-reviewed sym-lane work -**Prior:** the C+D standing-rooms ruling (xmesh docs/DESIGN-tenancy-standing-rooms.md, founder +**Date:** 2026-08-26 · **Author:** dev1 (sym runtime lane) · **Status:** v2 — mesh review SHIP-WITH-FIXES folded (x-review cmb-3e58586f…, 12 findings, all applied); DECIDED 2026-08-26: (1) grammar fix NOW, ahead of the gate; (2) ADOPT B AS FOLDED (owner-only, 24h lifetime, join-time expiry, handshake enforcement, default+sym public). Implementation = ordinary mesh-reviewed sym-lane work +**Prior:** the C+D standing-rooms ruling (xmesh docs/DESIGN-tenancy-standing-rooms.md, 2026-08-26) names this as its residual: "sym resolves rooms by string … full closure needs sym-side join authorization." This is that design. diff --git a/lib/config.js b/lib/config.js index f65c57c2..f74e8223 100644 --- a/lib/config.js +++ b/lib/config.js @@ -35,11 +35,11 @@ const { SYM_STATE_DIR: SYM_DIR } = require('./core/state-root'); /** DER/PKCS8 Ed25519 private-key header — the 16 bytes preceding the raw 32-byte key. */ const ED25519_PKCS8_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex'); -/** The founder's ruling, carried verbatim into the error an operator actually sees. */ +/** The rule behind the halt, stated in the error an operator actually sees. */ const IDENTITY_RULING = - 'An agent may lose its machine and recover its data, but if it loses its private key, it ' + - 'cannot prove that it is the same agent. Therefore the key must be preserved, and the ' + - 'system must refuse to start rather than silently create a replacement identity.'; + 'An agent can lose its machine and still recover its data, but an agent that loses its ' + + 'private key can no longer prove it is the same agent. The key must therefore be kept, and ' + + 'the system refuses to start rather than silently create a replacement identity.'; /** * Raised instead of minting a replacement identity. @@ -2064,7 +2064,7 @@ function acquireLockAt(lockPath, meta = {}, depth = 0) { } /** - * NAME-SUFFIXING IS DELETED (founder ruling, agent id = node id). + * NAME-SUFFIXING IS DELETED (agent id = node id). * * The name-suffixing resolver used to answer a same-host collision by returning * `-2`, `-3`, … That was never collision handling. An agent id names an AGENT, and diff --git a/lib/core/cmb-encoder.js b/lib/core/cmb-encoder.js index b881990b..05823bd9 100644 --- a/lib/core/cmb-encoder.js +++ b/lib/core/cmb-encoder.js @@ -90,7 +90,7 @@ function categoriesPreimage(categories) { } /** - * The prefix every key is minted with, FROZEN (founder canon §17). Post-cutover there is one + * The prefix every key is minted with, FROZEN (canon §17). Post-cutover there is one * form — bare `cmb-` + 64-hex — and no dual-emit window (§19.1/§20): the freeze window plus the * fleet census make an emission flag unnecessary, and a flag IS the backcompat §19 forbids. The * prefix says only "this is a CMB"; the scheme lives inside the signed payload, and any future diff --git a/lib/core/cmb-signing.js b/lib/core/cmb-signing.js index 6b041897..63218c69 100644 --- a/lib/core/cmb-signing.js +++ b/lib/core/cmb-signing.js @@ -440,7 +440,7 @@ function verifyCMB(cmb, publicKeyB64url) { : { signed: true, valid: false, error: 'legacy-key-rejected' }; } // UNREACHABLE BY MEASUREMENT — the legacy verification arm that stood here is DELETED - // (CTO ruling, 2026-08-07). Every record with `.metadata` returns from the v2 branch above; + // (decision 2026-08-07). Every record with `.metadata` returns from the v2 branch above; // every record with a top-level `.sig` and no `.metadata` returns `unverified-legacy` from // the grandfather branch. Nothing could reach what followed, so nothing behind this line // ever ran. diff --git a/lib/core/mood-frame.js b/lib/core/mood-frame.js index 0390270a..37923ea6 100644 --- a/lib/core/mood-frame.js +++ b/lib/core/mood-frame.js @@ -2,8 +2,8 @@ /** * @module sym/core/mood-frame - * @description The `mood` control frame (MMP §9.3; control-frame.schema.json, MMP 2.0 update 1 by the - * founder's ruling): `{ type, mood, context, timestamp }`, sealed on a confirmed Core Secure session. + * @description The `mood` control frame (MMP §9.3; control-frame.schema.json, MMP 2.0 update 1): + * `{ type, mood, context, timestamp }`, sealed on a confirmed Core Secure session. * * mood 1 to 1,024 characters * context at most 4,096 characters, or null diff --git a/lib/core/record-shape.js b/lib/core/record-shape.js index 38981337..0c2dce4f 100644 --- a/lib/core/record-shape.js +++ b/lib/core/record-shape.js @@ -47,10 +47,10 @@ function resolveCategories(record) { if (!record) return null; // ONE NAME. `categories` is the container and the only container. This used to walk a candidate - // list so a record could carry it under either name — that is the name-check logic the founder - // ruled out (2026-08-09, "remove all name check logic"), and removing it is what makes this - // module's promise true rather than aspirational. With one name there is no precedence to get - // wrong, and no second reader that can disagree with this one. + // list so a record could carry it under either name — that is the name-check logic removed on + // 2026-08-09, and removing it is what makes this module's promise true rather than + // aspirational. With one name there is no precedence to get wrong, and no second reader that + // can disagree with this one. // // A record carrying only the retired name does not resolve, and that refusal is the point: it // is legible at the boundary rather than half-read further in. @@ -76,7 +76,7 @@ function requireCategories(record, where) { * requireCategories so that "I can cope with absence" is stated at the call site rather than * implied by a truthiness test. * - * USAGE RULE — BINDING (CTO ruling, 2026-08-07). `hasCategories` MAY ONLY BE USED WHERE THE + * USAGE RULE — BINDING (decision 2026-08-07). `hasCategories` MAY ONLY BE USED WHERE THE * FALSE BRANCH RETURNS A VERDICT. It must NEVER guard a block that control can fall out of. * * if (!hasCategories(r)) return { valid: false, error: 'cannot-verify: …' }; // CORRECT diff --git a/lib/core/room-grant.js b/lib/core/room-grant.js index e17e73b6..1913bc45 100644 --- a/lib/core/room-grant.js +++ b/lib/core/room-grant.js @@ -3,7 +3,7 @@ /** * @module sym/core/room-grant * @description Ed25519-signed ROOM-JOIN grants — membership as a right, not as - * knowledge of a string (docs/DESIGN-room-join-authorization.md; founder ruling + * knowledge of a string (docs/DESIGN-room-join-authorization.md; decision * 2026-08-26, option B as folded). * * WHAT THIS EXISTS TO STOP, measured rather than imagined: a room is a NAME, and diff --git a/lib/frame-handler.js b/lib/frame-handler.js index 3fee8140..d9d86241 100644 --- a/lib/frame-handler.js +++ b/lib/frame-handler.js @@ -1998,7 +1998,7 @@ class FrameHandler { * * Identified 2026-07-05 in the liquid-substrate scope note, which ranked it bug-grade and * independent of any learning work; still present in published 0.12.1 when re-checked - * 2026-08-24. Founder decision D5: ship this fix on its own, and HOLD the recurrent-cell + * 2026-08-24. Decision D5: ship this fix on its own, and HOLD the recurrent-cell * dimension until the learning testbed says what shape it should be. * * WHAT THIS IS NOT, and the honesty matters more than the fix. This is still a STATELESS diff --git a/lib/interior.js b/lib/interior.js index 4ce28921..9cf98540 100644 --- a/lib/interior.js +++ b/lib/interior.js @@ -54,7 +54,7 @@ * deliveries that verified — a record signed to this node, or a room broadcast it admitted — never * a quarantined one. A recall item is { key, record, verified, storedAt, author }. * - * THE READ SIDE IS SCOPED TO THE MIND'S MISSION (final re-review, Finding 6; the founder's ruling C). + * THE READ SIDE IS SCOPED TO THE MIND'S MISSION (final re-review, Finding 6; ruling C). * A mind is the node's reasoning for ONE mission, and must not read what the node received for * another: * - each mind has its own READ VIEW: the deliveries that arrived while it runs (after its start), diff --git a/lib/node.js b/lib/node.js index 67d0e6d0..dcb5361d 100644 --- a/lib/node.js +++ b/lib/node.js @@ -483,7 +483,7 @@ class SymNode extends EventEmitter { // Retention — how long to keep CMBs in local storage. // - // DEFAULT: UNLIMITED (founder ruling 2026-07-20). Accumulated cognition is the + // DEFAULT: UNLIMITED (decision 2026-07-20). Accumulated cognition is the // most valuable asset a node holds; deleting it on a timer destroys the very // thing the system exists to build. The previous 86400s (24h) default was // silently pruning authority-bearing history — observed live: a deputy store @@ -679,7 +679,7 @@ class SymNode extends EventEmitter { this._inboxSeq = 0; this._inboxCursor = 0; this._inboxMax = opts.inboxMax || 500; - // DURABLE (founder ruling 2026-08-04): communication is addressed to the + // DURABLE (decision 2026-08-04): communication is addressed to the // NODE, and a new session RELINKS to it — including what was delivered // while no session was attached. The inbox was process memory only, so a // restart wiped the delivery feed while every sender believed it had @@ -2932,7 +2932,7 @@ class SymNode extends EventEmitter { oldestUndrainedAt: undrained.length ? (undrained[0].receivedAt ?? null) : null, // Distinct from "has a backlog": a node that has NEVER been drained is // not behind, it is unattended — the condition that hid five directed - // CMBs, two of them founder words, for three days. + // CMBs for three days. neverDrained: this._inboxCursor === 0 && this._inboxSeq > 0 && !(this._inbox.length > 0 && this._inbox.every((m) => m.acked)), dropped: this._metrics.inboxDropped ?? 0, @@ -3219,7 +3219,7 @@ class SymNode extends EventEmitter { // was not connected wrote the CMB locally, logged a single line, and returned // an entry INDISTINGUISHABLE from a delivered one; every layer above reported // success for sends that reached nobody. Measured cost (bl-a6e63608c8c): five - // directed CMBs to one seat — including a founder authorization and a founder + // directed CMBs to one seat — including an authorization and a // question — undelivered for three days while both senders believed otherwise, // each building a prose relay workaround around a channel reporting success. // diff --git a/lib/room-ownership.js b/lib/room-ownership.js index cc667d6b..2c125980 100644 --- a/lib/room-ownership.js +++ b/lib/room-ownership.js @@ -3,7 +3,7 @@ /** * @module sym/room-ownership * @description Which key owns which room — the receiver's local answer to - * "may this peer be here?" (docs/DESIGN-room-join-authorization.md, founder ruling + * "may this peer be here?" (docs/DESIGN-room-join-authorization.md, decision * 2026-08-26). * * WHAT OWNERSHIP IS FOR. Without an owner, a room's membership is knowledge of its diff --git a/tests/core-secure-final-review.test.js b/tests/core-secure-final-review.test.js index a75f2eb9..d7f1e1de 100644 --- a/tests/core-secure-final-review.test.js +++ b/tests/core-secure-final-review.test.js @@ -3,10 +3,9 @@ require('./_isolate-home'); // redirect $HOME before lib/config loads /** - * The final re-review of f0d936a (BLOCK): the founder's three rulings and the findings beside them, - * each as a regression test that fails on f0d936a. The reviewer's repros are in - * /private/tmp/claude-501/-Users-hongwei-sym-agent-a/sym-014-final-review/ (grant-probes.js P1-P4, - * node-probes.js Q1, digest-churn.js, fork-a, fork-b). + * The final re-review of f0d936a (BLOCK): the three design changes (rulings A, B and C) and the findings + * beside them, each as a regression test that fails on f0d936a. The reviewer's repros (grant-probes.js + * P1-P4, node-probes.js Q1, digest-churn.js, fork-a, fork-b) were written outside this repository. * * A, B retired with the time-replay grant rule (MMP §6.6; see below) * C the interior read side is scoped to the mind's mission (Finding 6) diff --git a/tests/delivery-ledger.test.js b/tests/delivery-ledger.test.js index 418cc9fe..e4759434 100644 --- a/tests/delivery-ledger.test.js +++ b/tests/delivery-ledger.test.js @@ -3,7 +3,7 @@ require('./_isolate-home'); // redirect $HOME before lib/config loads /** - * The redelivery and inbox-id bugs (founder, via agent-b, 2026-10; mesh-channel 0.10.1 on sym 0.13.16, + * The redelivery and inbox-id bugs (reported via agent-b, 2026-10; mesh-channel 0.10.1 on sym 0.13.16, * and reproduced on 0.14.0 at 6510bac): * * Bug 1: after a hot-swap (a new SymNode for the same identity) or a reconnect, CMBs already diff --git a/tests/directed-delivery-accountability.test.js b/tests/directed-delivery-accountability.test.js index c8b6cfb2..ee935b6c 100644 --- a/tests/directed-delivery-accountability.test.js +++ b/tests/directed-delivery-accountability.test.js @@ -19,7 +19,7 @@ require('./_isolate-home'); // redirect $HOME to a temp sandbox before lib/confi * the seat) therefore reported success for sends that reached no one. * * Live consequence: five directed CMBs addressed to the dev seat — including - * a founder authorization and a founder question — sat undelivered for three + * an authorization and a question — sat undelivered for three * days while every sender believed it had delivered. Two seats independently * built prose workarounds ("RELAY FOR DEV") around a channel that was * reporting success. diff --git a/tests/durable-inbox.test.js b/tests/durable-inbox.test.js index c8ae34f9..38dc2395 100644 --- a/tests/durable-inbox.test.js +++ b/tests/durable-inbox.test.js @@ -1,7 +1,7 @@ 'use strict'; /** - * Durable delivery inbox (founder ruling 2026-08-04): communication is + * Durable delivery inbox (decision 2026-08-04): communication is * addressed to the NODE, and a new session RELINKS to it — including what * was delivered while no session was attached. Before this, the inbox was * process memory only: a restart wiped the delivery feed while every sender diff --git a/tests/identity-halt.test.js b/tests/identity-halt.test.js index c40b72c1..ebe0a5dc 100644 --- a/tests/identity-halt.test.js +++ b/tests/identity-halt.test.js @@ -5,9 +5,9 @@ require('./_isolate-home'); // sandbox HOME/USERPROFILE before anything reads os /** * B-3 / AC-3.2 — the system refuses to start rather than replace an identity. * - * Founder ruling D-04: "An agent may lose its machine and recover its data, but if it loses its - * private key, it cannot prove that it is the same agent. Therefore the key must be preserved, - * and the system must refuse to start rather than silently create a replacement identity." + * Decision D-04: the private key is what makes an agent the same agent. An agent can lose its + * machine and still recover its data, but without its key it can no longer prove it is itself, so + * the system refuses to start rather than silently create a replacement identity. * * What this replaces was quiet and destructive: an unreadable identity.json logged a warning, * generated a fresh keypair, and OVERWROTE the file. The agent kept its name and lost the only @@ -65,8 +65,8 @@ test('unparseable identity HALTS and does not touch the file', () => { assert.throws(() => cfg.loadOrCreateIdentity('agent-b@mesh'), (e) => { assert.equal(e.name, 'IdentityHaltError'); assert.match(e.message, /REFUSING TO START/); - // The ruling itself is in the operator-facing text, not just in a doc. - assert.match(e.message, /refuse to start rather than silently create a replacement identity/); + // The rule itself is in the operator-facing text, not just in a doc. + assert.match(e.message, /refuses to start rather than silently create a replacement identity/); return true; }); @@ -163,7 +163,7 @@ test('AC-3.2: no `-2` identity can be created by any path', () => { test('the lease refuses a second holder and never suggests renaming', (t) => { // The old error advised "set a different SYM_NODE_NAME", which under agent-id-is-identity - // is advice to become a different agent — the exact move the ruling forbids. + // is advice to become a different agent — the exact move the rule forbids. // // The foreign holder is a real, live child process recorded with its real start time — the // current lock format exactly as a running holder writes it. This used to be PID 1 with a diff --git a/tests/identity-migration-by-id.test.js b/tests/identity-migration-by-id.test.js index 72d772c3..3a001bb4 100644 --- a/tests/identity-migration-by-id.test.js +++ b/tests/identity-migration-by-id.test.js @@ -11,8 +11,8 @@ require('./_isolate-home'); // redirect $HOME to a temp sandbox before lib/confi * every answer it did not recognise as "no such agent" and MINTED: it renamed the 0.13 directory (the old * identity.json still in it) onto the new node's path and wrote the new identity over the old key. * - * The tests below hold the line the founder drew ("the system must refuse to start rather than silently - * create a replacement identity"): a name that has an identity on this host never gets a new one. + * The tests below hold one line: the system refuses to start rather than silently create a replacement + * identity, so a name that has an identity on this host never gets a new one. * Every test uses its own agent name; the state root is shared by the file, as in config.test.js. */ diff --git a/tests/inbox-holder-accountability.test.js b/tests/inbox-holder-accountability.test.js index 241256a9..c774bff5 100644 --- a/tests/inbox-holder-accountability.test.js +++ b/tests/inbox-holder-accountability.test.js @@ -10,9 +10,8 @@ require('./_isolate-home'); // redirect $HOME to a temp sandbox before lib/confi * * Measured live 2026-08-09 — sym-daemon-mac: seq=96, cursor=0. Ninety-six * messages, never drained once, span 08-06 → 08-09 and still accumulating. - * Five of them were CMBs addressed directly to the dev seat, two carrying - * founder words. Every seat node beside it drained normally, so the condition - * was specific and detectable — nobody could detect it. + * Five of them were CMBs addressed directly to the dev seat. Every seat node beside it + * drained normally, so the condition was specific and detectable — nobody could detect it. * * Worse, the ring silently shredded its own backlog. _inbox.shift() evicted * oldest-first without asking whether the message had ever been read, so a diff --git a/tests/p6-legacy-grandfather.test.js b/tests/p6-legacy-grandfather.test.js index ec2e488f..426c4479 100644 --- a/tests/p6-legacy-grandfather.test.js +++ b/tests/p6-legacy-grandfather.test.js @@ -83,7 +83,7 @@ test('P-6: a pre-boundary block is REFUSED — grandfathering is retired', async // That policy required reading the audience under its retired name, because the companion case // below — a pre-boundary block from the WRONG audience — still had to be refused. Surfacing and // audience-checking together are only possible while the old name is still read, and the old - // name is gone (founder ruling 2026-08-09: remove all fallback and dead v1 code). + // name is gone (decision 2026-08-09: remove all fallback and dead v1 code). // // So the audience of such a record can no longer be established, and core refuses it as // `unreadable-audience` rather than treating an absent room as a broadcast. Refusing is the diff --git a/tests/product-name.test.js b/tests/product-name.test.js index f730c523..8f5543e5 100644 --- a/tests/product-name.test.js +++ b/tests/product-name.test.js @@ -1,7 +1,7 @@ 'use strict'; -// The product name is never "xMesh" (user, 2026-10-02: "don't use any xMesh, use xmesh in code, and XMesh in -// sentence and XMESH for brand or product name"). This walks every README, doc, comment and log string in the +// The product name is never "xMesh": it is XMesh in a sentence, XMESH for the brand or product name, and xmesh in +// code. This walks every README, doc, comment and log string in the // repo and fails on the camel-case spelling. Identifiers are untouched by it: the XMesh class, the // _handleXMeshInsight handler and XMESH_* names do not match, and nothing in the code is named xMesh. // Lowercase xmesh is the spelling for code (package, paths, CLI, wire ids) and is not checked here: telling a diff --git a/tests/remember-targeted.test.js b/tests/remember-targeted.test.js index 09c77e0b..acf8b183 100644 --- a/tests/remember-targeted.test.js +++ b/tests/remember-targeted.test.js @@ -124,8 +124,8 @@ describe('remember({to}) — MMP §4.4.4 targeted CMB send', () => { // test asserted the local write and stopped, so a GREEN here certified a // send that reached nobody and reported success. The store surviving a // partition is necessary and was never the question — the question is - // whether the CALLER can tell. Five directed CMBs, two of them founder - // words, were lost for three days under this green. + // whether the CALLER can tell. Five directed CMBs were lost for three days + // under this green. assert.strictEqual(entry.delivery.undelivered, true, 'a directed send that reached no peer must be reported as undelivered'); assert.strictEqual(entry.delivery.dispatched, 0, 'no frame was even handed to a transport'); diff --git a/tests/room-join-auth.test.js b/tests/room-join-auth.test.js index e040c9c3..ef4329e9 100644 --- a/tests/room-join-auth.test.js +++ b/tests/room-join-auth.test.js @@ -2,8 +2,8 @@ /** * Room join authorization — the grant (core/room-grant.js) and the receiver's - * ownership registry (room-ownership.js). Founder ruling 2026-08-26, option B as - * folded by the mesh review; every ruled property is pinned here. + * ownership registry (room-ownership.js). Decision 2026-08-26, option B as + * folded by the mesh review; every decided property is pinned here. */ const { describe, it } = require('node:test'); diff --git a/tests/spec-update-attest.test.js b/tests/spec-update-attest.test.js index b50bf3a1..f8303e0d 100644 --- a/tests/spec-update-attest.test.js +++ b/tests/spec-update-attest.test.js @@ -3,7 +3,7 @@ require('./_isolate-home'); // redirect $HOME before lib/config loads /** - * sym-attest-v1 as MMP 2.0 update 1 registers it (#27), and the founder's rulings for it: what this + * sym-attest-v1 as MMP 2.0 update 1 registers it (#27), and the decisions for it: what this * node never attests or relays (S3), the wire grammar (closed attestation, lowercase UUID signers, a * role of up to 64 characters, a method of up to 32), equivocation relayed once as evidence, the * scope of an attester's role judged on the record its assertion names, and no checkpoint root over diff --git a/tests/spec-update-frames.test.js b/tests/spec-update-frames.test.js index 69f45170..b5f57367 100644 --- a/tests/spec-update-frames.test.js +++ b/tests/spec-update-frames.test.js @@ -4,7 +4,7 @@ require('./_isolate-home'); // redirect $HOME before lib/config loads /** * The session frames as MMP 2.0 update 1 (meshcognition-website main at c07ff20) defines them: the - * mood frame by the founder's ruling, cmb-fetch and cmb-fetch-result as their schemas close them, + * mood frame, cmb-fetch and cmb-fetch-result as their schemas close them, * fetched records verified by the whole of §8.8.5 (D4), cmb-anchors as §9.4 sends it, a clear 1011 * acted on only when it names this node's session or answers its probe, sealed ping and pong, and the * control envelope's bounds. Each test fails on 958592a. @@ -40,7 +40,7 @@ function capture(session) { } const HEX = () => crypto.randomBytes(32).toString('hex'); -describe('the mood frame: {type, mood, context, timestamp} (founder ruling, #26)', () => { +describe('the mood frame: {type, mood, context, timestamp} (#26)', () => { it('is sent with no sender fields, and the text and context are bounded', async () => { try { const A = node('m-a'); const B = node('m-b'); diff --git a/tests/spec-update-remix-tether.test.js b/tests/spec-update-remix-tether.test.js index b72499ff..e7f513dd 100644 --- a/tests/spec-update-remix-tether.test.js +++ b/tests/spec-update-remix-tether.test.js @@ -3,7 +3,7 @@ require('./_isolate-home'); // redirect $HOME before lib/config loads /** - * §15.8 on the remix path (founder ruling, 0.14.0): remix() is asynchronous and evaluates what it + * §15.8 on the remix path (0.14.0): remix() is asynchronous and evaluates what it * produces against its nearest verified lineage root before it is minted. A faithful remix keeps its * lineage, a remix drifted past the reject floor is minted as a fresh root, and one with no verified * root in reach keeps its lineage, unverified; each verdict is signed as an mmp-tether-v1 attestation. diff --git a/tests/spec-update-session.test.js b/tests/spec-update-session.test.js index 9da8dc0a..ac2915e2 100644 --- a/tests/spec-update-session.test.js +++ b/tests/spec-update-session.test.js @@ -4,7 +4,7 @@ require('./_isolate-home'); // redirect $HOME before lib/config loads /** * sym 0.14.0 aligned with the MMP 2.0 update 1 drafts it implements (the session and sealed-control - * set: #26, #30, #31, #37, #23), the founder's rulings for that update, and the reviews of each draft + * set: #26, #30, #31, #37, #23), the decisions for that update, and the reviews of each draft * against sym 390b4af. Each test here fails on 390b4af. */ diff --git a/tests/wire-fromname.test.js b/tests/wire-fromname.test.js index 5267a2b0..5e5218e1 100644 --- a/tests/wire-fromname.test.js +++ b/tests/wire-fromname.test.js @@ -25,7 +25,7 @@ const { admitAs, deliver, identity } = require('./_core-secure'); describe('a frame\'s fromName is taken as text (R5)', () => { // 0.14: frames arrive on a confirmed session (design D1); the session's proven name is the fallback. - // MMP 2.0 update 1 (founder ruling): the mood frame names no sender. A mood frame carrying any + // MMP 2.0 update 1: the mood frame names no sender. A mood frame carrying any // fromName is not that frame and is refused; the event is labelled with the session's proven name. it('mood: a frame naming a sender is refused, whatever its fromName; the session\'s name labels the rest', () => { const name = uniq('fromname-mood'); From ed0ebe3c5ee0c84e70c4b5a25dda0ad7c41e9113 Mon Sep 17 00:00:00 2001 From: "SYM.BOT" Date: Sat, 17 Oct 2026 13:23:53 +0100 Subject: [PATCH 2/3] Add a guard test: no tracked file cites the owner tests/no-owner-citings.test.js scans git ls-files (a directory walk when there is no .git, as in an export) and fails on a ruling, a bug report, quoted words, a dated attribution or a personal name. "founder" is deny-by-default: every occurrence must be covered by the allowed vocabulary (identifiers, env vars, the founder key/root/anchor role, nodes named 'founder', the detector's keywords) or a per-file allowance. tests/fixtures/**, package-lock.json and binaries are skipped. The scanner is exercised on a planted tree: each kind of citing is caught, the vocabulary passes, and a citing on a line that also holds vocabulary is still caught. Against the tree before the rewrite the guard reports every citing, including the ones the plain search missed. --- tests/no-owner-citings.test.js | 230 +++++++++++++++++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 tests/no-owner-citings.test.js diff --git a/tests/no-owner-citings.test.js b/tests/no-owner-citings.test.js new file mode 100644 index 00000000..52484d7e --- /dev/null +++ b/tests/no-owner-citings.test.js @@ -0,0 +1,230 @@ +'use strict'; + +// The library never cites its owner. A public package carries what was decided and why, not who said it: no +// ruling or decision credited to a person, no quoted words, no personal name or address, in a comment, a doc, the +// CHANGELOG, a test title or an error string. This scans every tracked file (git ls-files; a directory walk when +// there is no .git, as in an export of the package) and fails on a citing. +// +// "founder" is also a ROLE in the authority model and part of the product's vocabulary, so the scan is deny-by-default: +// every "founder" in a line must be covered by the allowed vocabulary below, or it is a hit. The allowed vocabulary: +// - identifiers and env vars: founderAction, _meta.founderAction, detectFounderAction, SYM_FOUNDER_ANCHOR +// - the role: "founder key", "founder root", "founder anchor", "1-of-1 founder case" +// - a node, task owner or source literally named 'founder', and the detector's keywords 'brief founder' and +// 'brief the founder' +// - per-file entries (ALLOW_IN_FILE) for the words that describe the founderAction signal itself +// Skipped: tests/fixtures/** (hash-pinned vendored vectors), package-lock.json, binary files, node_modules, and this file +// (its own patterns would match themselves; the negative tests below run the same scanner on a planted tree). + +const { describe, it } = require('node:test'); +const assert = require('node:assert'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const { execFileSync } = require('node:child_process'); + +const ROOT = path.join(__dirname, '..'); +const SELF = 'tests/no-owner-citings.test.js'; + +const SKIP_DIRS = new Set(['node_modules', '.git', 'coverage']); +const SKIP_PREFIXES = ['tests/fixtures/']; +const SKIP_FILES = new Set(['package-lock.json', SELF]); +const BINARY = /\.(png|jpe?g|gif|ico|webp|pdf|woff2?|ttf|otf|zip|gz|tgz|mp4|mov)$/i; + +// "founder" as vocabulary: stripped from a line before the line is judged. +const VOCABULARY = [ + /[A-Za-z0-9_]*[Ff]ounder[A-Z][A-Za-z0-9_]*/g, // founderAction, detectFounderAction + /[A-Z0-9_]*(?:_FOUNDER|FOUNDER_)[A-Z0-9_]*/g, // SYM_FOUNDER_ANCHOR + /\bfounder (?:key|root|anchor|case)s?\b/gi, // the role in the authority model + /'(?:brief (?:the )?)?founder'/gi, // a node, task owner or source named 'founder'; the detector's keywords +]; + +// Words that describe the founderAction signal itself, allowed in the one file that implements it. +const ALLOW_IN_FILE = [ + { file: 'lib/mesh-agent.js', re: /Founder Action Detection|founder attention|the founder needs to see/gi, + reason: 'describes the founderAction signal (a CMB a human should see)' }, +]; + +// The owner's names and handles, built from parts so that this file holds none of them whole. +const NAMES = [['hong', 'wei'].join(''), ['hong', ' wei'].join(''), ['mx', 'michael'].join(''), ['symbot', 'dev'].join(''), 'michael']; + +// Other citings, judged on the whole line. +const RULES = [ + { name: 'personal name or address', re: new RegExp(NAMES.join('|'), 'i') }, + { name: 'the user as the source of a decision', re: /\bthe user (?:said|asked|ruled|wants|approved|decided|told)\b|\buser ruling\b|\buser(?:'|’)s (?:ruling|word|rule)\b/i }, + { name: 'an executive or the owner as the source of a ruling', re: /\b(?:ceo|cto|coo|boss)(?:'s|’s)? (?:ruling|rulings|ruled|decision|decided|said|canon|approval)\b|\b(?:owner|user)(?:'s|’s)? (?:ruling|rulings|ruled|canon)\b/i }, + { name: 'a request or approval attributed to someone', re: /\b(?:as|per) (?:requested|asked|ruled|decided|instructed) by\b|\bper the (?:user|owner|ceo|cto|boss)\b/i }, + { name: 'a dated attribution, "(user, 2026-10-02: ...)"', re: /\((?:user|owner|boss|ceo|cto|coo),? *\d{4}-\d{2}-\d{2}/i }, +]; + +/** The files of a tree: git ls-files when it is a checkout, a directory walk when it is not. */ +function listFiles(root) { + if (fs.existsSync(path.join(root, '.git'))) { + try { + return execFileSync('git', ['ls-files', '-z'], { cwd: root, encoding: 'utf8', maxBuffer: 1 << 26 }) + .split('\0').filter(Boolean); + } catch { /* git missing or not usable: walk */ } + } + const out = []; + const walk = (dir, rel) => { + for (const e of fs.readdirSync(dir, { withFileTypes: true })) { + if (SKIP_DIRS.has(e.name)) continue; + const r = rel ? `${rel}/${e.name}` : e.name; + if (e.isDirectory()) walk(path.join(dir, e.name), r); + else out.push(r); + } + }; + walk(root, ''); + return out; +} + +const skipped = (rel) => SKIP_FILES.has(rel) || BINARY.test(rel) || SKIP_PREFIXES.some((p) => rel.startsWith(p)); + +/** Every citing in `root`: [{ file, line, rule, text }]. */ +function scan(root, files = listFiles(root)) { + const hits = []; + for (const rel of files) { + if (skipped(rel)) continue; + let text; + try { + if (!fs.statSync(path.join(root, rel)).isFile()) continue; + text = fs.readFileSync(path.join(root, rel), 'utf8'); + } catch { continue; } // tracked but not on disk (a staged removal) + if (text.includes('\0')) continue; // binary + const allow = ALLOW_IN_FILE.filter((a) => a.file === rel); + text.split('\n').forEach((raw, i) => { + const at = (rule) => hits.push({ file: rel, line: i + 1, rule, text: raw.trim().slice(0, 160) }); + let residue = raw; + for (const re of VOCABULARY) residue = residue.replace(re, ' '); + for (const a of allow) residue = residue.replace(a.re, ' '); + if (/founder/i.test(residue)) at('"founder" outside the allowed vocabulary'); + for (const r of RULES) if (r.re.test(raw)) at(r.name); + }); + if (RULES.some((r) => r.re.test(rel))) hits.push({ file: rel, line: 0, rule: 'personal name in a file name', text: rel }); + } + return hits; +} + +const show = (hits) => hits.map((h) => `${h.file}:${h.line} [${h.rule}] ${h.text}`).join('\n'); + +describe('the library does not cite its owner', () => { + it('finds the files it checks', () => { + const files = listFiles(ROOT).filter((f) => !skipped(f)); + assert.ok(files.length >= 100, `scanned ${files.length} files`); + }); + + it('no tracked file cites the owner (a ruling, a bug report, quoted words, a name)', () => { + const hits = scan(ROOT); + assert.deepStrictEqual(hits, [], `state the decision or the rule, not who gave it:\n${show(hits)}`); + }); + + it('every per-file allowance still matches something (the allowlist does not rot)', () => { + for (const a of ALLOW_IN_FILE) { + const p = path.join(ROOT, a.file); + if (!fs.existsSync(p)) continue; // an export without this file + assert.ok(new RegExp(a.re.source, a.re.flags.replace('g', '')).test(fs.readFileSync(p, 'utf8')), + `${a.file}: allowance for "${a.reason}" matches nothing; remove it`); + } + }); +}); + +describe('the scanner itself (a planted tree, no .git: the directory-walk path)', () => { + const plant = (files) => { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'sym-citings-')); + for (const [rel, body] of Object.entries(files)) { + fs.mkdirSync(path.dirname(path.join(dir, rel)), { recursive: true }); + fs.writeFileSync(path.join(dir, rel), body); + } + return dir; + }; + const withTree = (files, fn) => { + const dir = plant(files); + try { return fn(dir); } finally { fs.rmSync(dir, { recursive: true, force: true }); } + }; + + // The words the scanner looks for are built from parts, so this file holds no citing of its own. + const T = { + f: 'found' + 'er', F: 'Found' + 'er', FF: 'FOUND' + 'ER', u: 'us' + 'er', + name: ['Hong', 'wei'].join(''), mail: ['symbot', 'dev@gmail.com'].join(''), handle: ['mx', 'michael'].join(''), + }; + const CITINGS = { + 'a ruling in a comment': `// the key is kept (${T.f} ruling 2026-08-04)`, + 'a ruling in capitals': `**Status:** v2; ${T.FF} RULED 2026-08-26: ship it`, + 'a possessive ruling': `/** The ${T.f}'s ruling, carried into the error. */`, + 'a decision label': ` * ${T.F} decision D5: ship this fix on its own`, + 'a changelog sentence': `The ${T.f} ruled three design changes.`, + 'a bug report': `### Redelivery (${T.f}'s bug report, 2026-10)`, + 'a relayed report': ` * The bugs (${T.f}, via agent-b, 2026-10; mesh-channel 0.10.1)`, + 'words attributed to the owner': ` * two of them ${T.f} words, lost for three days`, + 'a line the owner drew': ` * hold the line the ${T.f} drew`, + 'a canon reference': ` * FROZEN (${T.f} canon §17)`, + 'a statement credited to the user': `// the ${T.u} said to drop the fallback`, + 'a ruling credited to the user': `// per the ${T.u} ruling, no suffixes`, + 'a dated user attribution': '// the name is never spelled so (user, 2026-10-02: "use it in code")', + 'an executive ruling': '// (CTO ruling, 2026-08-07)', + 'a requested-by line': '// added as requested by the boss', + 'the owner\'s name': `// agreed with ${T.name} on the call`, + 'the owner\'s address': `contact: ${T.mail}`, + 'a handle': `// ask ${T.handle}`, + }; + + for (const [what, line] of Object.entries(CITINGS)) { + it(`catches ${what}`, () => { + const hits = withTree({ 'lib/x.js': `'use strict';\n${line}\nmodule.exports = 1;\n` }, (dir) => scan(dir)); + assert.strictEqual(hits.length >= 1, true, `not caught: ${line}`); + assert.ok(hits.every((h) => h.file === 'lib/x.js' && h.line === 2), show(hits)); + }); + } + + it('catches a citing in a doc, a test title and the CHANGELOG, and in a file name', () => { + const hits = withTree({ + 'docs/a.md': `The ${T.f} ruled that it ships.\n`, + 'tests/t.test.js': `it('does it (${T.f} ruling, #26)', () => {});\n`, + 'CHANGELOG.md': `- **Thing** (${T.f} ruling 2026-08-27)\n`, + [`notes-${T.name.toLowerCase()}.md`]: 'clean text\n', + }, (dir) => scan(dir)); + assert.deepStrictEqual([...new Set(hits.map((h) => h.file))].sort(), + ['CHANGELOG.md', 'docs/a.md', `notes-${T.name.toLowerCase()}.md`, 'tests/t.test.js']); + }); + + it('lets the vocabulary through: identifiers, the role, a node named founder, the detector\'s keywords', () => { + const clean = [ + "if (!meta?.founderAction && detectFounderAction(categories)) meta.founderAction = true;", + "const pin = process.env.SYM_FOUNDER_ANCHOR || null;", + "// One founder key (the 1-of-1 case). The nodeId lets that node act as the anchor itself.", + "canonization is reserved to anchor authority (the founder root or an anchor-rooted grantee)", + "new SymNode({ name: 'founder', anchor: { threshold: 1, keys: [] } });", + "t.agent !== 'founder'", + "f.source = 'founder';", + "'founder', 'human', 'decision', 'brief founder', 'brief the founder',", + "/** A node pinned as its own 1-of-1 anchor (the founder case). */", + "_meta.founderAction=true ONLY if your role definition's rules for it are satisfied", + ].join('\n'); + assert.deepStrictEqual(withTree({ 'lib/x.js': clean + '\n' }, (dir) => scan(dir)), []); + }); + + it('lets a per-file allowance through in its own file only', () => { + const body = '// Auto-detect whether a CMB warrants founder attention.\n'; + assert.deepStrictEqual(withTree({ 'lib/mesh-agent.js': body }, (dir) => scan(dir)), []); + assert.strictEqual(withTree({ 'lib/other.js': body }, (dir) => scan(dir)).length, 1); + }); + + it('still catches a citing on a line that also holds vocabulary', () => { + const hits = withTree({ 'lib/x.js': `// founderAction is set by the ${T.f}'s ruling\n` }, (dir) => scan(dir)); + assert.strictEqual(hits.length, 1); + }); + + it('skips the vendored fixtures, the lockfile, binaries and node_modules', () => { + const hits = withTree({ + 'tests/fixtures/vec.json': `{"note":"${T.f} ruling"}\n`, + 'package-lock.json': `{"name":"${T.f} ruling"}\n`, + 'docs/pic.png': `${T.f} ruling`, + 'node_modules/x/index.js': `// ${T.f} ruling\n`, + }, (dir) => scan(dir)); + assert.deepStrictEqual(hits, []); + }); + + it('reports the file, the line and the rule', () => { + const hits = withTree({ 'docs/a.md': `fine\nfine\nThe ${T.f} said so.\n` }, (dir) => scan(dir)); + assert.deepStrictEqual(hits.map((h) => [h.file, h.line, h.rule]), [['docs/a.md', 3, '"founder" outside the allowed vocabulary']]); + }); +}); From 91e508892c9d379ba2cb52de538174e98dd06fb1 Mon Sep 17 00:00:00 2001 From: "SYM.BOT" Date: Sat, 17 Oct 2026 13:27:46 +0100 Subject: [PATCH 3/3] Remove two more attributions to a seat (a measurement, a gate); guard them --- lib/core/record-shape.js | 2 +- lib/node.js | 2 +- tests/no-owner-citings.test.js | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/core/record-shape.js b/lib/core/record-shape.js index 0c2dce4f..455f73d8 100644 --- a/lib/core/record-shape.js +++ b/lib/core/record-shape.js @@ -94,7 +94,7 @@ function hasCategories(record) { /** WHY the categories could not be read, as three distinct states rather than one falsy. * - * The distinction the CTO measured (2026-08-07): an E2E record verified BEFORE decryption + * The distinction measured on 2026-08-07: an E2E record verified BEFORE decryption * carries its container as a base64 CIPHERTEXT STRING with `_e2e` attached. `hasCategories` * correctly reports false — you must not be able to content-verify ciphertext — but reporting * that as "record carries no CAT7 container" MISDESCRIBES it. The container is right there; it diff --git a/lib/node.js b/lib/node.js index dcb5361d..349c9e81 100644 --- a/lib/node.js +++ b/lib/node.js @@ -3237,7 +3237,7 @@ class SymNode extends EventEmitter { // so it counts frames handed to a transport, not frames any peer received. // Naming that "delivered" would be this exact class of overclaim one layer // up: a count bound to something other than what its name asserts. The word - // `delivered` stays reserved until an ack earns it (CTO gate on a371021). + // `delivered` stays reserved until an ack earns it (gate on a371021). // // `undelivered` is still sound as written, and is the load-bearing category: // never-dispatched does entail never-delivered, even though the converse diff --git a/tests/no-owner-citings.test.js b/tests/no-owner-citings.test.js index 52484d7e..698eb209 100644 --- a/tests/no-owner-citings.test.js +++ b/tests/no-owner-citings.test.js @@ -51,7 +51,7 @@ const NAMES = [['hong', 'wei'].join(''), ['hong', ' wei'].join(''), ['mx', 'mich const RULES = [ { name: 'personal name or address', re: new RegExp(NAMES.join('|'), 'i') }, { name: 'the user as the source of a decision', re: /\bthe user (?:said|asked|ruled|wants|approved|decided|told)\b|\buser ruling\b|\buser(?:'|’)s (?:ruling|word|rule)\b/i }, - { name: 'an executive or the owner as the source of a ruling', re: /\b(?:ceo|cto|coo|boss)(?:'s|’s)? (?:ruling|rulings|ruled|decision|decided|said|canon|approval)\b|\b(?:owner|user)(?:'s|’s)? (?:ruling|rulings|ruled|canon)\b/i }, + { name: 'an executive or the owner as the source of a ruling', re: /\b(?:ceo|cto|coo|boss)(?:'s|’s)? (?:ruling|rulings|ruled|decision|decided|said|canon|approval|gate|measured)\b|\b(?:owner|user)(?:'s|’s)? (?:ruling|rulings|ruled|canon)\b/i }, { name: 'a request or approval attributed to someone', re: /\b(?:as|per) (?:requested|asked|ruled|decided|instructed) by\b|\bper the (?:user|owner|ceo|cto|boss)\b/i }, { name: 'a dated attribution, "(user, 2026-10-02: ...)"', re: /\((?:user|owner|boss|ceo|cto|coo),? *\d{4}-\d{2}-\d{2}/i }, ]; @@ -161,6 +161,7 @@ describe('the scanner itself (a planted tree, no .git: the directory-walk path)' 'a ruling credited to the user': `// per the ${T.u} ruling, no suffixes`, 'a dated user attribution': '// the name is never spelled so (user, 2026-10-02: "use it in code")', 'an executive ruling': '// (CTO ruling, 2026-08-07)', + 'an executive gate': '// stays reserved until an ack earns it (CTO gate on a371021)', 'a requested-by line': '// added as requested by the boss', 'the owner\'s name': `// agreed with ${T.name} on the call`, 'the owner\'s address': `contact: ${T.mail}`,