Skip to content

feat(plugin-audit): sys_activity gains writer-stamped read-rule columns with an idempotent backfill, and the withheld-row rule is a predicate with no SYSTEM pre-scan (ADR-0139 E6) - #23814

Draft
objectstack-fleet[bot] wants to merge 12 commits into
mainfrom
claude/issue-23731-activity-changed-fields
Draft

objectstack-fleet[bot] wants to merge 12 commits into
mainfrom
claude/issue-23731-activity-changed-fields

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23731

Clause-②: yes

ADR-0139 E6, ruled letter B (ruling record 6205262047, maintainer 「B (推荐)」; Q2 = b confirmed). The activity stream's withheld-row rule becomes a predicate over two writer-stamped sys_activity columns. The SYSTEM pre-scan and its 2,000-row window are gone, for both halves.

Rollout (the changeset's first paragraph)

Run os migrate activity-changed-fields --apply at once after upgrading. Until it runs, stored activity rows carry no stamp and are withheld from every non-system reader. The door's name and flags are os migrate audit-metadata-bodies's shape, one for one (--database-url, --apply, --yes, --force, --json); nothing is renamed, so objectstack-ai/cloud#2900 reads it as declared. It is a dry run by default, idempotent, and covers both columns in one run. One measured difference, stated in the changeset: the columns arrive with the upgraded deployment's schema sync, which --apply performs itself, so a dry run against a database no upgraded process has opened cannot read them and exits 1.

What changed

  • sys_activity gains changed_fields (tags) and milestone_key (text), both readonly. They are stamped by ONE planner, planActivityStamp (activity-changed-fields.ts):

    • changed_fields is the key union of the recorded old / new for an update. It is [] for a create, a delete, a note, or an update that touched only internal fields.
    • milestone_key is the sorted, comma-joined watched-field list (text_sources.kind) of a fired milestone the mirror declared. It is null otherwise.
    • A milestone row that names no readable list cannot be judged. It stays unstamped and fails closed.
  • Writers. The CRUD mirror stamps its own rows. A sys_activity before-insert hook (Q2 = b) stamps every inserted row, over whatever value a writer sent: the engine's create-side readonly strip skips sys_ objects, so the hook is the guard, as comment-access-hooks.ts is for is_edited. Both stamp only when the registered sys_activity declares the columns, the organization_id precedent.

  • The rule (activity-field-redaction.ts). It is now computeWithheldRowFilter → keptRowsOfParent: one branch per parent object the read gate enumerates, or the one parent a read pins on object_name. A row is kept when all of these hold:

    • it is stamped;
    • its changed_fields is empty, or contains a served field (one $contains per served field);
    • its milestone_key is null, or equals the key of P's CURRENT activityMilestones declaration while the reader is served every field that declaration watches.

    It reads no row and composes no leaf. A parent the service gives no answer for is kept whole, as before. An engine with no registry view is denied, and that is said once.

  • The redaction narrows changed_fields to the served members and never serves milestone_key.

  • The query guard (parent-field-query-guard.ts) covers both columns, and the pin against the redaction's fail-closed strip covers them too.

  • The backfill, backfillActivityChangedFields, sits beside stored-metadata-body-migration.ts and is exported from src/index.ts. It reads unstamped rows a page at a time (keyset on id) and updates both columns in one write, as the platform.

  • The door, os migrate activity-changed-fields, is registered in the one-shot family table, the JSON-purity family and the absent-database roster.

  • Docs and generated files. deployment/cli.mdx (a section, and the data-migrations table) and the platform checklist variants (16 → 17 subcommands). The plugin-audit i18n bundles, regenerated and translated for zh-CN / ja-JP / es-ES (the provenance hashes dropped by themselves). The tenant-audit census, regenerated: the backfill's update is one more write call site (225 → 226), and the hand-written prose figures follow. permissions/system-context.mdx row 45 no longer names a pre-scan of the activity redaction.

Landing places: everything is in plugin-audit, the CLI door, dogfood, docs and the generated files the gates regenerate. None of packages/spec, objectql, core, any driver, parent-record-read-gate.ts, comment-access-hooks.ts, or migrate/apply.ts is touched.

Evidence

All readings are at head 70ff93d (merge base 609d07a), on the shared box, under scripts/pm/os-verify-lock.sh.

  • pnpm --filter @objectstack/plugin-audit test: 51 files / 832 tests passed. typecheck exit 0, test-typecheck ledger included.

  • @objectstack/cli typecheck exit 0. Unit project: 297 files / 4,379 tests passed.

  • CLI integration, run locally because the diff touches it:

    • the new door suite: 4 / 4;
    • data-commands.absent-database and schema-migrate.one-shot-family: 135 / 135;
    • json-stdout-purity.e2e: 59 / 59 (nightly tier).
  • @objectstack/dogfood typecheck exit 0.

  • Real stack (dogfood), with the real SecurityPlugin + AuditPlugin and a store of more than 2 × 2,000 rows:

    • a member is served every row it may read past the old window, with that total on every page;
    • an administrator's site-wide read (newest first, top 20) is served the 20 newest rows, and a total equal to the fixture's 4,203 rows plus the rest;
    • no SYSTEM read of the stream reads a row's change, and none carries a ReadableIds leaf. The only SYSTEM read left is the read gate's D5 bounded probe of the parent pair.

    The seven activity dogfood suites, the org-peer sign-in case (activity-withheld-update.dogfood) among them: 7 files / 65 tests passed.

  • The upgrade path, measured. On a database turned back into the previous version's shape (both columns dropped from the rotation shard, the view re-created without them, rows planted unstamped):

    • --apply's plain boot adds both columns to the shard and re-creates the view;
    • it stamps each row exactly as the writer stamps a new one;
    • a re-run stamps nothing, and still counts the one row it cannot judge.

Ablations. Each negative pin was run red, then restored. Every mutation went through scripts/ablation-replace.mjs; restore was proven blob == HEAD with git diff HEAD empty.

leg mutation red
A1 update half always true 9 tests
A2 stamped conjunct dropped 3
A3 milestone key equality loosened 2
A4 changed_fields served unnarrowed 1
A5 milestone_key served 1
A6 hook keeps a writer's own stamp 2
A7 stamped columns dropped from the query guard 1 (the PERMISSION_DENIED / 403 probe)
A8 a SYSTEM pre-scan re-injected into the rule dogfood pin red: a SYSTEM read of id, object_name, metadata carrying a ReadableIds leaf

A8 resolves through dist/. Its marker was present in dist/ (ablation-dist-preflight.mjs), and after the restore and rebuild it was absent. Two attempts were no-ops and are void:

  • A8's first leg: the replacement contained its own anchor, the tool refused it, and the pre-flight showed dist/ unchanged;
  • A5's first leg: refused the same way, so nothing ran.

Acceptance notes


Generated by Claude Code

claude added 11 commits October 16, 2026 02:44
…atabase families

Claude-Session: https://claude.ai/code/session_01TyhsxCqpTkk1U5GLD1JETy
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TyhsxCqpTkk1U5GLD1JETy
Co-authored-by: Claude <noreply@anthropic.com>
… backfill's write site; system-context page

Claude-Session: https://claude.ai/code/session_01TyhsxCqpTkk1U5GLD1JETy
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/xl documentation Improvements or additions to documentation tests tooling labels Oct 16, 2026
@github-actions

github-actions Bot commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/cli, @objectstack/plugin-audit, touching 61 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/plugins/plugin-audit/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

39 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json d795719926a2dacf7a394e23b717f36961343a26.

⛔ 7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/plugins/plugin-audit/src/index.ts) — pages documenting those are invisible to this run
  • 11 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 33 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d795719926a2dacf7a394e23b717f36961343a26 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1a058c00c3aa2ff0b3007b8940a254a4b38a6ea8 — the merge of head d75ae9784332b0ba3d0eeb48efb8d1ee978eed5c into base d795719926a2dacf7a394e23b717f36961343a26, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 1a058c00c3aa2ff0b3007b8940a254a4b38a6ea8 && git checkout 1a058c00c3aa2ff0b3007b8940a254a4b38a6ea8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d795719926a2dacf7a394e23b717f36961343a26 d75ae9784332b0ba3d0eeb48efb8d1ee978eed5c && git checkout -B drift-repro d795719926a2dacf7a394e23b717f36961343a26 && git merge --no-ff d75ae9784332b0ba3d0eeb48efb8d1ee978eed5c

node scripts/docs-audit/affected-docs.mjs --json d795719926a2dacf7a394e23b717f36961343a26

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d795719926a2dacf7a394e23b717f36961343a26 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: TIER_DEFAULT
Head-sha: 70ff93d3b03432d2b930d17e2605590f4a174e74
Local-runs: none

Rendered at CONTRACT_REVIEW_TIER by the ruling's item 5 (6205262047 on #23731, letter B, maintainer 「B (推荐)」); the face is the Clause-②: yes declaration (no packages/spec/src/** path, no governed path on the file list), so the tier named above is the face's. Read in this act: card #23731's body and all 10 comments; PR #23814's body, file list (28 paths) and net diff against main (merge base 609d07a7d); the 40 check-runs on the head, latest per name. Scope: every path of the code diff (the two new plugin-audit modules, activity-field-redaction.ts, activity-read-visibility.ts, audit-writers.ts, parent-field-query-guard.ts, objects/sys-activity.object.ts, index.ts, the four generated translations, the five plugin-audit tests, packages/cli/src/commands/migrate/activity-changed-fields.ts with its test and three family registrations, the dogfood pin) plus, read on origin/main: parent-record-read-gate.ts, served-fields.ts, comment-access-hooks.ts, stored-metadata-body-migration.ts, cli/.../migrate/audit-metadata-bodies.ts, cli/src/utils/absent-table-reads.ts, data-migration-plugins.ts, objectql/src/engine.ts and validation/rule-validator.ts (the readonly strips), plugin-security/src/system-write-guard.ts, driver-sql/src/sql-driver.ts and core/src/utils/json-membership-sql.ts, spec/src/data/field-value.zod.ts, ADR-0139, scripts/check-adr-symbol-anchors.mjs. The changeset's prose and the docs are the lane seat's ACCEPT except the changeset's Clause-②: line and level, judged under ②. Stamp of this act: 2026-10-16T04:55Z.

① Derived judgments

  • The two columns on the platform object sys_activity — right. changed_fields (type: 'tags', readonly, not required) and milestone_key (Field.text, readonly, not required), both under the Context group, both arriving through the ordinary schema sync (measured on the rotation shard in activity-changed-fields.integration.test.ts, cases 1 and 2). changed_fields is served narrowed to the reader's served members; milestone_key is deleted from every row the redaction reaches (redactActivityRows, before the parent check) and is therefore never served to a reader the redaction runs for. Both are NULL when unstamped, and [] is stored as a value, not as NULL, which the stamped conjunct relies on.
  • The exported backfill — right. backfillActivityChangedFields(engine, logger, { apply, pageSize }) and ACTIVITY_BACKFILL_PAGE_SIZE, with the two report/engine types, planActivityStamp, the two column constants and ActivityStamp, all added to src/index.ts. It is the shape of migrateStoredMetadataBodyCopies: SYSTEM-context read and single-id SYSTEM update through the engine (the append-only object's migration door, never the CRUD write path), dry run by default, no sys_migration flag, keyset paging on id over changed_fields $null: true, both columns in one update. Idempotent by construction (a stamped row leaves the unstamped set; an unjudged row stays in it and is counted each run). A refused page read is counted as a failure and the door exits 1 — the pin a read the table refuses is a failure, never a clean report holds it.
  • The read rule, both halves, against letter B and ADR-0139 D4 — right. keptRowsOfParent(P, served, watched) is, per parent, object_name = P AND changed_fields IS NOT NULL AND (changed_fields $empty OR one $contains per served field) AND (milestone_key IS NULL, or, only when the reader is served every field of P's CURRENT activityMilestones declaration, milestone_key IS NULL OR milestone_key = key(current declaration)). The update half is D4's sentence exactly (a non-empty changed_fields none of whose members is served is withheld; [] for a create, a delete, a note and an internal-only update is kept). The milestone half is the ruling's item 1 exactly (key equality against the CURRENT declaration AND every watched field served; otherwise withheld; an object with no current declaration serves no milestone row). The unstamped conjunct is D4's unjudged-row answer (fail closed). computeWithheldRowFilter ORs one branch per parent the read gate enumerates (enumerateParentObjects(engine, ACTIVITY_GATE), the gate's own exclusions), or the one parent the read pins (pinnedParentObject, the query guard's reading); a parent the service gives no answer for is kept whole; no answer for any parent returns null (nothing narrowed). An engine with no registry view is denied once and loudly. The predicate compiles to exact membership on driver-sql: tags is in MULTI_OPTION_TYPES, which JSON_COLUMN_TYPES spreads, so $contains takes jsonMembershipPredicate (SQLite json_each, PostgreSQL's jsonb containment operator, MySQL JSON_CONTAINS) and $empty the multi_value arm, on all three modelled dialects; a served field that is a prefix of a withheld one cannot match.
  • No SYSTEM-composed read or leaf remains in the rule — right. The redaction no longer imports CommentAccessEngine's find or PARENT_GATE_SCAN_LIMIT; ActivityRedactionEngine is the registry view plus getSchema. The one SYSTEM read left on the stream is the read gate's own D5 bounded probe of the parent pair, which is ADR-0139 D5's and not this card's; the dogfood pin asserts exactly that and that no SYSTEM read carries a ReadableIds leaf. The 2,000-row window is gone from the rule (activity-withheld-row-predicate.dogfood: a member served past the window with one total on every page; an administrator's top-20 newest with total past the window).
  • The redaction and the query guard cover both columns — right. VALUE_BEARING_COLUMNS carries both, so the fail-closed strip takes them too (activity-predicate-guard.test.ts pins it); ACTIVITY_QUERY_GUARD.columns spreads ACTIVITY_STAMPED_COLUMNS, so a predicate, sort, group or aggregation over either is refused PERMISSION_DENIED / 403 to a reader withheld a parent field (pinned, matching and non-matching probes, find and count).
  • A writer cannot set either column — right. On insert, the sys_activity before-insert hook assigns the planner's answer over whatever any writer sent, in every context (pinned with a SYSTEM-context insert carrying its own values). The engine's create-side static-readonly strip returns no subject for a sys_ object (staticReadonlyInsertSubject), so readonly: true is NOT what guards the columns on insert — the hook is, as the PR says, the is_edited precedent. On update, a user-context write to an append-only object is refused by plugin-security's engine-owned write guard (ADR-0103) wherever the security service is composed; the backfill's SYSTEM update is the one sanctioned writer. The mirror and the hook stamp only when the registered sys_activity declares both columns (the organization_id precedent), read per call.
  • The CRUD mirror's stamp — right. planActivityStamp(activityMetadata) over the same { old, new, kind, text_sources } the row records; the watched list a fired milestone declares now comes from declaredMilestoneWatchedFields(objectDef), the same function the rule keys the CURRENT declaration by, with the same asked predicate (field and summary strings) matchMilestone used on main, so a row written under the declaration in force compares equal.
  • The door os migrate activity-changed-fields — right. Flags one for one with audit-metadata-bodies (--database-url, --apply, --yes, --force, --json), dry run by default on the read-only boot (deferSchemaDdl + readOnlyProbe), absentTableReads for the rotation-managed table, a non-zero failures count exits 1, registered in the one-shot family table, the JSON-purity family and the absent-database roster. The spelling objectstack-ai/cloud#2900 reads is unchanged.
  • The derived gate families on this head — one WRONG. Check-runs, latest per name, none pending at the final read of this act: 34 success (all six Test Core shards among them), 5 skipped (Console Pin Gate, the dogfood matrix shards and Verify CLI, packed-tarball smoke, Temporal Conformance), and two failure: Lint · docs & code guards and its aggregate Lint & Repo Gates. The red step is ADR symbol anchors resolve (no line numbers survive) (scripts/check-adr-symbol-anchors.mjs). Cause, read deterministically off the diff and docs/adr/0139-engine-internal-semi-join-leaf-readable-parent-ids.md on main: the diff deletes computeWithheldUpdateFilter and isWithheldOnlyUpdate from activity-field-redaction.ts (replaced by computeWithheldRowFilter and keptRowsOfParent), and ADR-0139 anchors both — line 73 (consumer table, row 3, #computeWithheldUpdateFilter) and line 191 (D4, #isWithheldOnlyUpdate); neither symbol occurs on the head, so both are [unresolved-symbol]. The PR body's accept-set claim that the ADR could be left alone because it is Tier H is wrong as landed: the gate reads the ADR's anchors against the tree, so a rename of an anchored symbol is a change the anchor must follow. The gate's own remedy text: update the anchor (re-point it to where the mechanism now lives) — a docs/adr/** edit, Tier H under Prime Directive feat: Comprehensive CRM example demonstrating all ObjectStack protocol features #14 (one Tier H path makes the whole PR Tier H, so it is the maintainer's word on this PR or a sibling PR landed first) — or keep the two symbol names resolvable in the file; ⛔ never an anchor-exempt marker, which is maintainer-only and not for a live anchor. The other anchors into touched files (audit-writers.ts#AUDIT_EXCLUDED_OBJECTS, #auditRow, #diff; activity-read-visibility.ts#installActivityReadVisibility) resolve on the head.
  • Observations, not reasons (the lane's call): (a) an unpinned read builds one $contains leaf per served field per enumerated parent, each an EXISTS, jsonb-containment or JSON_CONTAINS construct, so the WHERE of an administrator's header-bell read grows with parents times fields; measured with one parent in the dogfood, named in the PR body, not measured on an app-sized registry. (b) The pins' field names (awp_open / awp_secret, f_open / f_admin) cannot tell membership from substring; the membership reading is established by driver-sql's construct, not by a pin — one served field that is a prefix of a withheld one would pin it. (c) In a composition with no security service the rule and the redaction do not run, so milestone_key is served there to context-less callers; consistent with the module's standing rule and the no-answer case, stated under ③ Q2.

② Semver level

minor for @objectstack/plugin-audit and @objectstack/cli matches what the diff publishes: two columns on a published platform object, new package exports (the backfill, the planner, the constants and types), a new CLI subcommand, no removal from any public surface (on main, src/index.ts exports nothing from activity-field-redaction.ts, so computeWithheldUpdateFilter, isWithheldOnlyUpdate, isWithheldMilestone, isWithheldRow and ActivityRedactionEngine were package-internal, as the changeset says). Clause-②: yes on the PR body and on the changeset, no arm, and yes takes at least minor — right; not breaking, so no ADR-0087 disposition is owed. The ruling's item 3 binds the changeset's FIRST paragraph and the first paragraph carries it whole: the rollout consequence (rows written before the upgrade are withheld from every non-system reader until the backfill runs), the door os migrate activity-changed-fields, dry run by default, --apply to write, idempotent, both columns in one run, "run at once after upgrading", plus the measured difference (a dry run on a database no upgraded process has opened exits 1). Item 1's cost (older-declaration and since-removed-field milestone rows withheld until retention, 14 days declared, lifecycle.retention_overrides may lengthen it) is stated. packages/qa/dogfood is not a released package.

③ Boundary flags

  • Q1 (the planner's stamp always overrides a writer-sent value) — A, right. It is a superset of the ruling's "for any writer that does not": every writer is stamped, and a writer that stamps is re-derived. B would let any server action relax the read rule for its own rows, which the ruling's floor forbids (item 2: "a row the planner cannot judge stays unstamped and fails closed" presumes the planner's answer is the row's). Pinned (ablation A6; the SYSTEM-context insert carrying changed_fields: [] and a key reads back null / null).
  • Q2 (no security service, or no field answer for a parent: unstamped rows not withheld) — A, right on the merits. The ruling's item 3 sentence "withheld from every non-system reader" is the rollout consequence for a served deployment, where every non-system reader is one the security service answers for; a parent the service gives no answer for has no withheld field to protect, and the module's standing rule — "a reader the service gives no answer for is narrowed by neither", restated in permissions/system-context.mdx row 45 — already serves such a reader every row whole, withheld updates and milestones included. Withholding unstamped rows there would empty every feed of a security-less composition and protect nothing; it relaxes no access boundary, because none exists where no field answer exists. The changeset's paragraph holds for every deployment that composes the security service (the cloud v18 move among them). Accepted.
  • Deviation 1 (docs and generated surfaces outside the claim's list) — accepted: deployment/cli.mdx, system-context.mdx row 45, the tenant-audit census (225 to 226, the backfill's update is the new site) and docs/qa/platform-checklist/areas/cli.json (16 to 17) are gate-regenerated or the lane's prose; the translations are the i18n gates' regeneration (green).
  • Deviation 2 (three family registrations) — right: a new migrate door must join the one-shot family table, the JSON-purity family and the absent-database roster, or those suites red.
  • Deviation 3 (ACTIVITY_GATE exported; the security(plugin-audit): an activity row whose every recorded change is withheld from the reader is still served (empty change, summary, actor, timestamp), so an org peer reads WHEN a colleague's identity row was stamped — each sign-in time #21388 header rewritten) — right: the rule enumerates parents through the gate's own enumeration, one source of truth.
  • Deviations 4 to 7 (package-local tsc and check runs outside the verify lock; the detached primary checkout; the model-free trailer pair; one merge of origin/main) — process notes consistent with AGENTS.md; nothing to answer.
  • The out-of-scope finding (stale prose in parent-record-read-gate.ts and ADR-0139's consumer row 3) — escalated, not noted: the ADR half of it is exactly the anchor that reds this head (①, FAIL reason). parent-record-read-gate.ts's no-object-admission prose stays ADR-0139 E7: the comment-thread, comment-reaction and attachment read gates emit one ReadableIds leaf per parent object and do no pre-scan; D5's three classes keep the probe (D4, D5) #23732's / ADR-0139 execution: the engine-internal semi-join leaf across spec, engine, drivers and consumers (E0–E7 coordination) #22788's. The report's gate line "pnpm check:adr-anchors exited 0" names the presence gate (check-adr-anchors.mjs), not the symbol gate (check:adr-symbol-anchors), and the 103-gate union did not carry the red one; CI did.
  • CI at the final read of this act: every check completed; all six Test Core shards green; every other check green or skipped except the two named reds, which share the one cause above.

Implemented-by: session_01TyhsxCqpTkk1U5GLD1JETy
Reviewed-by: session_018VqJPGA1NDNSCZsnecsChP

VERDICT: FAIL

FAIL reasons:

  1. Gate red on the head: ADR symbol anchors resolve (no line numbers survive) — ADR-0139's anchors activity-field-redaction.ts#computeWithheldUpdateFilter (line 73) and #isWithheldOnlyUpdate (line 191) no longer resolve, because the diff deletes both symbols. Clear it by re-anchoring the ADR (Tier H: the maintainer's word, on this PR or a sibling landed first) or by keeping the two symbol names resolvable in the file; not by an exemption marker. Everything else judged above is right and lands unchanged once the gate is green.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · REWORK (patch round 1) · domain:services seat 2 · session_018VqJPGA1NDNSCZsnecsChP · 2026-10-16T04:56Z

At head 70ff93d3b0. The contract record 6208399171 (FAIL) judges everything right except one gate, and this seat's own read agrees. Form: draft, base main, Fixes #23731, Clause-②: yes; 28 files, all on claim 6206767603's surface or the gate-regenerated and prose surfaces the report lists. check-governed-merges.mjs: NOT governed, 2,367 changed lines. The changeset's first paragraph carries ruling item 3 whole, checked sentence by sentence against the diff. The deployment/cli.mdx row's "No flag" means no migration-flag record, as the summary-nulls row uses it.

Patch round 1, both items in one push:

  1. The red gate (ADR symbol anchors resolve, the only cause of Lint · docs & code guards and Lint & Repo Gates). ADR-0139 cites activity-field-redaction.ts#computeWithheldUpdateFilter (line 73) and #isWithheldOnlyUpdate (line 191), and the diff deletes both. Keep both names resolvable in that file, on the functions that carry those roles now (the withheld-row filter builder; the update-half predicate). ⛔ No alias kept only for the anchor, ⛔ no exemption marker, ⛔ no edit under docs/adr/** (Tier H, the maintainer's hand). Run pnpm check:adr-symbol-anchors locally; it is not in the derived set (dispatch-gates.mjs is frozen by ruling 208, so no card is filed for that).
  2. One membership pin (the record's observation b): a served field whose name is a prefix of a withheld one (f served, f_secret withheld). An update of only f_secret stays withheld; an update of f is kept. Ablate it with a substring match in place of membership; it must go red.

Not asked: observation (a), the WHERE that grows with parents × served fields, stays an Acceptance note with the dogfood's one-parent measurement. The stale prose in parent-record-read-gate.ts stays #23732's / #22788's. ADR-0139's consumer row 3 prose ("its own SYSTEM pre-scan") is now stale Tier H text; this seat raises it to the maintainer at ACCEPT.

A same-form contract review on the new head follows the push.


Generated by Claude Code

…at carry those roles; pin membership over a prefix

computeWithheldUpdateFilter keeps its name as the withheld-row predicate
builder, and isWithheldOnlyUpdate is the update half over changed_fields,
called by keptRowsOfParent. A served field whose name is a prefix of a
withheld one keeps no withheld-only update row.

Claude-Session: https://claude.ai/code/session_01TyhsxCqpTkk1U5GLD1JETy
Co-authored-by: Claude <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests tooling

Projects

None yet

2 participants