Skip to content

fix(spec)!: every filter door refuses a $now anchor that carries a key beyond $now / addDays - #26592

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-34f9ql
Oct 27, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/project-thread-34f9ql

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread
Fixes #26413

Clause-②: no (narrowing) — every filter door refuses a $now anchor carrying a key beyond $now / addDays, which it ran before. Nothing is widened.
Metadata-protocol: narrowed (a filter saved through FilterConditionSchema may no longer carry a key beside $now and addDays on a now-anchored comparand)
Skills-docs: none — skills/objectstack-query/rules/filters.md (the refusal list under "Now-Anchored Comparands") already teaches that "any extra key (there is no subDays)" is refused with INVALID_FILTER / 400, and eval filters-pagination-search.json already forbids subDays; this diff makes that sentence true at every door, so it needs no edit.

维护者速读

  • 改了什么: 所有过滤入口共用的 $now 锚点检查(isWellFormedNowAnchor)现在拒收 $now / addDays 之外的任何键,报 INVALID_FILTER / 400 并点名多余的键。
  • 为什么改: NowAnchorSchema 声明为封闭,但入口只看 addDays,subDays: 7 这类键被静默忽略,查询按 addDays 跑完、不报错(ADR-0049:声明即须执行)。
  • 风险与回滚: 收窄:之前带多余键能跑的查询和能保存的过滤现在被拒;合法锚点不受影响。回滚即还原 filter-comparand-type.ts 一处。
  • 先看: packages/spec/src/data/filter-comparand-type.ts(isWellFormedNowAnchor 与 malformedNowAnchorRefusal 新分支)、packages/objectql/src/engine-now-anchor.test.ts(五个入口的钉)、.changeset/26413-now-anchor-extra-key-refused.md。
  • 两分钟验证: pnpm --filter @objectstack/spec exec vitest run src/data/filter-comparand-type.test.ts,预期新的 [#26413] 组全绿。
  • 需要你做的: 无,席位落地。

What changes

  • packages/spec/src/data/filter-comparand-type.ts: isWellFormedNowAnchor now answers false for an anchor with any key other than $now / addDays, so the comparand-type walk that every filter door runs refuses it. malformedNowAnchorRefusal gains an undeclared-key arm, checked first: it carries an undeclared key "subDays" — the anchor takes $now and addDays only (there is no subDays: a negative addDays subtracts). No new error code: the doors keep INVALID_FILTER / 400. The key set is restated locally (filter.zod.ts imports this module) and the test reconciles it against NowAnchorSchema.shape.
  • Doors this reaches, all through the one walk: find() / count() / update() in object and array form, aggregate()'s where, aggregations[i].filter, having, and FilterConditionSchema on save. A well-formed anchor and its resolved form ($now holding the pinned ISO instant) run as before.
  • @objectstack/spec changeset, minor (see Acceptance notes on the level).

Tests

  • packages/spec/src/data/filter-comparand-type.test.ts: new [#26413] group: the door's key set equals NowAnchorSchema.shape; refusal with code, status and the named key at all six scalar comparisons for an integer offset, a { $field } offset and the resolved form; under $and / $or / $not and through both parseFilterAST forms; every undeclared key named, before a malformed offset; well-formed anchors kept by reference.
  • packages/spec/src/data/filter.test.ts: the FilterConditionSchema save door refuses subDays and note, naming the key, beside the existing closed-schema pin; the clean anchor still parses.
  • packages/objectql/src/engine-now-anchor.test.ts: new [#26413] group, one answer at every door: find() object form (driver never called), find() array form, aggregate() where, aggregations[i].filter, having, each with code, status and the named key, and each with the clean anchor still running.
  • packages/formula/src/matches-filter-now-anchor.test.ts: narrowed, not retired. Measured: it does not contradict the refusal. The engine reaches this evaluator only through having-filter.ts (having and the aggregations[i].filter rows fallback), both behind the door, and the CEL lowering for the RLS check emits no anchor. The assertion stays; the title and a comment now say the evaluator sits behind the door and does not re-judge the keys, instead of presenting the tolerance as the anchor's contract.

Acceptance notes

  • Level vs. the order. The dispatch said "a patch changeset" and "Metadata-protocol as measured". Measured: FilterConditionSchema.safeParse({ opened_at: { $lt: { $now: true, addDays: -30, subDays: 7 } } }) was success: true on base and is refused now, so a value a metadata author could save is refused: narrowed. AGENTS.md's level table makes a declared kind at least minor on a moved package, so the changeset is @objectstack/spec: minor. objectql and formula change tests only and publish nothing.
  • ADR-0087: not-required (no-migration-prescription): no key is added, removed or renamed (NowAnchorSchema declared exactly two keys since 18.2.0), and an extra key never had a meaning at any executor, so no rewrite keeps what the author meant.
  • Noted, not fixed (from the earlier report on this card, unchanged by this diff): the bare implicit-equality spelling { opened_at: { $now: true, addDays: -30, note: 'x' } } is read by the walk as an operator bag, not a comparand, so it is outside this refusal. having already refuses the bare spelling; its answer at where against a real driver was not measured.

Verification

Readings at head 98b536ff. Builds and tests ran under os-verify-lock.

Package tests and typecheck
  • pnpm --filter @objectstack/spec test: exit 0, 729 files, 22055 passed, 1 todo.
  • pnpm --filter @objectstack/formula test: exit 0, 54 files, 1452 passed.
  • pnpm --filter @objectstack/objectql test: exit 0, 472 files, 8794 passed (after pnpm --filter '@objectstack/objectql^...' build).
  • pnpm --filter @objectstack/{spec,formula,objectql} typecheck: exit 0 each.
  • pnpm exec eslint --no-inline-config on the five changed source and test files: exit 0 (the repo-wide pnpm lint is CI's run).
Ablation (one, on the guard line)

Committed fix first, then node scripts/ablation-replace.mjs replaced if (undeclaredNowAnchorKeys(value).length > 0) return false; with a branch that never fires (marker ABLATED_26413). Anchor 1 to 0, blob a7e88059 to 64df538e; spec rebuilt; ablation-dist-preflight found the marker in 2 built files. Expected direction red; observed red: spec 3 failed / 221 passed (the three new refusal pins), objectql 5 failed / 15 passed (all five new door pins). Restore: blob equals HEAD, git diff HEAD empty, spec rebuilt, preflight --absent clean over 580 built files, whole-tree git status --porcelain empty.

Derived gates

node scripts/pm/dispatch-gates.mjs --commands derived 82 families; 80 exit 0. check:dual-build-cjs-loads and check:lean-entry-closure exit 3 (prerequisite: they read whole-repo built output; this worktree built the objectql closure only), NOT MEASURED locally, left to CI. --ran reconciliation: 82 derived, 80 run, 2 NOT-MEASURED, 0 UNRUN. check-adr-0087-registration --base origin/main: one declared-breaking changeset, disposition carried. Local-only: check-skill-frame-freshness exit 0; check-dev-prereqs exit 1 because the workspace is not fully built (37 packages lack dist/), an environment state, not a finding. The full dogfood battery reading goes in the os-dev report on the card.

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic.
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes.
  • 4. One issue, one branch off an up-to-date main, one pull request.
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset.
  • 6. packages/spec touched: its generated artifacts are regenerated and committed.
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English.
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…y beyond $now / addDays

isWellFormedNowAnchor read addDays only, so { $now: true, addDays: -30,
subDays: 7 } passed the comparand-type walk that every filter door runs
(find object and array form, aggregate where, aggregations[i].filter,
having, and FilterConditionSchema on save) and ran on the addDays reading
alone, while NowAnchorSchema declares the anchor closed. The walk now
refuses any other key with INVALID_FILTER / 400, and the refusal names
the key and says the anchor takes $now and addDays only.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 6 documentable anchor(s).

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/automation/flows.mdx (via addDays (literal, a string literal in NOW_ANCHOR_KEYS))
  • content/docs/data-modeling/fields.mdx (via addDays (literal, a string literal in NOW_ANCHOR_KEYS))
  • content/docs/data-modeling/formulas.mdx (via addDays (literal, a string literal in NOW_ANCHOR_KEYS))
  • content/docs/protocol/objectql/query-syntax.mdx (via addDays (literal, a string literal in NOW_ANCHOR_KEYS))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via addDays (literal, a string literal in NOW_ANCHOR_KEYS))
  • content/docs/releases/v18/18-2.mdx (via addDays (literal, a string literal in NOW_ANCHOR_KEYS))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 55 of 207 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 — 147 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 68edbcf10fdc8351d6238c6a9a34e0cc66c71bb0 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4245e590d1b9132fbbf868f5cc8798b51c08f93c — the merge of head 98b536ffd50ac587f83c82e55b5aadaa5db73118 into base 68edbcf10fdc8351d6238c6a9a34e0cc66c71bb0, 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 4245e590d1b9132fbbf868f5cc8798b51c08f93c && git checkout 4245e590d1b9132fbbf868f5cc8798b51c08f93c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 68edbcf10fdc8351d6238c6a9a34e0cc66c71bb0 98b536ffd50ac587f83c82e55b5aadaa5db73118 && git checkout -B drift-repro 68edbcf10fdc8351d6238c6a9a34e0cc66c71bb0 && git merge --no-ff 98b536ffd50ac587f83c82e55b5aadaa5db73118

node scripts/docs-audit/affected-docs.mjs --json 68edbcf10fdc8351d6238c6a9a34e0cc66c71bb0

⚠️ 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 68edbcf10fdc8351d6238c6a9a34e0cc66c71bb0 → 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: CONTRACT_REVIEW_TIER
Head-sha: 98b536ffd50ac587f83c82e55b5aadaa5db73118
Local-runs: none
Scope: packages/spec/src/data/filter-comparand-type.ts + packages/spec/src/data/filter.zod.ts (NowAnchorSchema, the FilterConditionSchema save door at parseFilterAST), packages/objectql/src/engine.ts (lowerWhereFilterArray, the aggregations[i].filter and having doors), packages/objectql/src/having-filter.ts, packages/core/src/utils/filter-tokens.ts (resolveNowAnchor), packages/drivers/driver-sql/src/sql-driver.ts (nowAnchorOf), packages/formula/src/matches-filter.ts, packages/plugins/plugin-security/src/rls-compiler.ts (judgeCompiledComparands), packages/spec/src/shared/strict-object.ts (closedObject), skills/objectstack-query/rules/filters.md

Inputs read: card #26413 (body and all six comments, the two os-dev reports and the withdrawal note included), PR #26592 (body, file list, net diff against main at this head), and the check-runs on the head. The rest of the diff (the changeset and the three test files outside spec) is the lane seat's ACCEPT at TIER_DEFAULT.

Check-runs at the time of this record: every required job is completed and green, including Lint and Repo Gates, TypeScript Type Check, Check Changeset, Governed Surface Queue Guard, Spec property liveness, Dogfood Regression Gate, Build Core and all eight Test Core shards. Test Core (5/8) was still in progress on my first read and finished green before this record was closed. The skipped jobs are the UI, docs, temporal and opt-in lanes that this diff does not reach.

① Derived judgments

  1. Accept-set change at the comparand-type walk. isWellFormedNowAnchor now answers false for any key other than $now and addDays, and malformedNowAnchorRefusal gains a first arm that names every undeclared key. Read in the live tree, this is one function on one walk, and the walk is the one every platform filter door runs. Right.

  2. Which doors this reaches. I traced each one in the cited code. lowerWhereFilterArray in engine.ts runs normalizeFilterComparandTypes on the object form and parseFilterAST on the array form, and it is called by find, findOne, update, delete, count and aggregate. The aggregations[i].filter door runs the walk at engine.ts near line 20456 and the having door at line 20539. parseFilterAST in filter.zod.ts runs the walk on everything it returns, and the FilterConditionSchema save door goes through it. The RLS compiler's judgeCompiledComparands also runs the walk, for using and check alike. So the PR's door list is right, and it is in fact one door short in its own favour: the RLS compile seam refuses the same anchor too. The changeset's "every filter door" claim holds.

  3. The door runs before the resolver. lowerWhereFilterArray runs ahead of resolveWhereFilterTokens, and resolveNowAnchor in filter-tokens.ts spreads the node unchanged apart from pinning $now. So the authored form is judged, and the resolved form the engine hands the executors carries no system-added key that the walk could trip on. The test for the resolved form ($now holding an ISO string plus note) pins the same refusal. Right.

  4. Executors are unchanged and consistent. nowAnchorOf in driver-sql, resolveNowAnchor in core, having-filter.ts and the formula evaluator all read $now and addDays by shape and nothing else. None of them needs a change, because no extra key reaches them through a door any more. Right.

  5. The formula evaluator's tolerance stays, scoped. matchesFilterCondition called directly still reads an anchor by shape. Every in-repo caller sits behind a door that runs the walk: having-filter.ts (having and the per-aggregation fallback), and plugin-security's write check, whose parts come out of judgeCompiledComparands. The explain engine and the tenant wall hand it engine-built filters that carry no anchor. The retitled test says exactly this. Right, and the card's "retire or narrow" wording allows it.

  6. Public surface. NOW_ANCHOR_KEYS and undeclaredNowAnchorKeys are module-private. No export, type or error code is added or renamed. The refusal reuses INVALID_FILTER / 400. No generated spec artifact moves. Right.

  7. The restated key set. NOW_ANCHOR_KEYS is a local copy of NowAnchorSchema's two keys, because filter.zod.ts imports this module and the reverse import would be a cycle. The reconciliation test reads NowAnchorSchema.shape. I checked that closedObject builds a ZodObject subclass from the same def and that lazySchema proxies property reads to the real instance, so .shape is live. The drift guard is real. Right.

  8. The skill sentence. skills/objectstack-query/rules/filters.md line 78 already says any extra key is refused with INVALID_FILTER / 400, and the eval forbids subDays. This diff makes the sentence true. Skills-docs: none is right. The Docs Drift advisory lists four hand-written pages that name addDays; I read query-syntax.mdx and it says there is no subDays, which agrees with the new refusal. Nothing to edit.

② Semver level

@objectstack/spec: minor. Right.

  • Metadata-protocol: narrowed is measured, not asserted: FilterConditionSchema accepted { $now: true, addDays: -30, subDays: 7 } on base and refuses it now, and a filter is something a metadata author writes (views, datasets, policies). AGENTS.md Post-Task Checklist step 3 makes a declared kind at least minor on the package the diff moves. The dispatch said patch, and the dev was right to deviate and said so in the report. The dispatch's Metadata-protocol: as measured by the dev left the kind to the measurement.
  • Clause-②: no (narrowing) matches the diff: nothing widens, and an input that ran now refuses. The triage comment's yes (narrowing) was the earlier reading; the question the line answers is whether the card widens the accept set or the public surface, and the answer is no. The PR body and the changeset carry the same line, byte for byte on the value and the arm.
  • BREAKING banner, fix(spec)!: title and one ADR-0087 marker, not-required (no-migration-prescription), are present in the changeset body. The disposition is sound: no key is added, removed or renamed, an extra key had no meaning at any executor, so there is no FROM to TO rewrite. check-adr-0087-registration and Check Changeset are green on the head.
  • objectql and formula change tests only and publish nothing. No changeset owed.

③ Boundary flags

  • Report 6436343165 (needs_decision, option A or B). Answered by the re-laning note 6436359662 and the spec-seat claim 6437371136: option A, the shared walk. This PR is option A. Closed.
  • The dev's deviation from patch to minor. Answered in ② above: the rule in AGENTS.md decides, and the dev followed it. Closed.
  • The formula pin, "narrowed, not retired". Answered in ① item 5: every in-repo caller sits behind a door that runs the walk. Closed.
  • Out-of-scope note, carried from the earlier report: the bare implicit-equality spelling { opened_at: { $now: true, addDays: -30, note: 'x' } } is read by the walk as an operator bag and is not judged here. Read in the cited code, it is not a silent run either: the $now key reaches the drivers as an operator name, and driver-sql's nowAnchorPositionError and having-filter.ts line 1780 refuse it. Its answer on driver-memory is not measured. A note in the acceptance notes is the right carrier under Prime Directive 10; a card is owed only if someone measures a silent answer. Not escalated.
  • open_questions on the final report: empty. Nothing left to escalate.

④ Design judgment

packages/spec/src/data/filter-comparand-type.ts

The change holds against the code it cites and the scenario it is accepted against: an author writes subDays: 7 on a now-anchor, and every door answers one INVALID_FILTER / 400 that names the key. I tried to break it along these lines.

  • Order of the arms. Undeclared keys are checked before the addDays arm, in both the predicate and the refusal, so { $now: true, subDays: 7 } is told about subDays first, which is the key the author got wrong. The refusal lists every undeclared key, not just the first. Good.
  • Guard agreement. isNowAnchor admits an object whose $now is true or a string, which is exactly the shape driver-sql's nowAnchorOf reads, so a refusal here is never for a shape an executor would have accepted. The predicate and the refusal read one helper, undeclaredNowAnchorKeys, so they cannot disagree on the set.
  • Keys with an undefined value. Object.keys counts { note: undefined }, so it is refused with note named while the JSON preview omits it. Zod's strict object refuses the same key, so the walk agrees with the declaration. Not reachable from JSON. Not a finding.
  • Copy-on-write contract. The anchor branch answers keep or refuse and never narrows, so a well-formed anchor still returns the same reference; the new test pins it.
  • Cycle and drift. The local key set is the price of filter.zod.ts importing this module, and the shape reconciliation test is the drift guard. The comment says why. Good.
  • Refusal length. The canonical sentence with the engine prefix, find('ticket'): ... subDays ..., is 496 characters, four under the 500-character client bound the module header cites. A longer field name, a path under $and, or a second undeclared key pushes it over, and the client then drops the tail, which is the "NOT applied" sentence. The load-bearing parts (path, preview, key names, the two-key rule) are front-loaded, so the author still gets the key, and the sibling arms sit at a similar length. The fixed clause "(there is no subDays: a negative addDays subtracts)" costs 52 characters and is printed even when the only extra key is note. should-fix: make that clause conditional on subDays being among the keys, or shorten it, so the common one-key case stays inside the bound with the "NOT applied" sentence intact. Not a must-fix: no door answers wrong rows, and the refusal still names the key.
  • Stale sentence. The isNowAnchor docblock still says a malformed anchor is refused by its own schema sentence and by each executor; the next docblock says the walk refuses it HERE. The second is the one that is true for every door. should-fix: trim the first to match, on the next touch.

No must-fix found. The tree does what the card's option (a) asked for and what the skill already taught.

Implemented-by: claude/project-thread-34f9ql
Reviewed-by: session_01WGufzfhobWEgUUrjz4rrbh

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · ACCEPT · domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-27T09:54Z

Head 98b536ffd50ac587f83c82e55b5aadaa5db73118. Card #26413 (option (a), triage 6433326089), claim 6437371136 amended by 6438758345, dev report 6438607776.

  • Contract review: owed at CONTRACT_REVIEW_TIER for two reasons. packages/spec/src/data/filter-comparand-type.ts is touched, and the card is a narrowing. PASS 6438749334 names this head. The shared comparand walk now refuses any $now anchor key beyond $now / addDays, and names the extra key first. The reviewer traced it through every door: find, findOne, update, delete, count, aggregate, aggregations[i].filter, having, the FilterConditionSchema save door and the RLS compile seam. The walk judges the authored form before the token resolver runs. No export or error code moves. The formula evaluator's by-shape read stays only behind those doors, and its pin is narrowed, not retired.
  • Two should-fixes, not carried: (1) the one-key refusal is 496 characters, close to the 500-character client bound. The fixed "there is no subDays" clause prints even when the key is note, so a long path can cut off the "NOT applied" tail, but the key is still named first. (2) The isNowAnchor docblock has one stale sentence. Both are below the filing bar. Take them on the next touch of this file.
  • Deviation from the order, accepted: the order said patch. The dev measured the save door narrowing, so Metadata-protocol: narrowed and '@objectstack/spec': minor per AGENTS.md step 3. The claim is amended to match. There is a BREAKING banner and one ADR-0087 marker (not-required (no-migration-prescription)), and there is no key to rewrite.
  • Shape: Fixes #26413, 6 files. Clause-②: no (narrowing) and Metadata-protocol: narrowed on the body, the changeset and the amended claim. Skills-docs: none, because filters.md already taught the refusal and now it is true. check-governed-merges --pr 26592: NOT governed, 196 changed lines.
  • Gates: check-runs on the head are 40 success, 8 skipped and 0 red. mergeable_state is clean.
  • Landing: Tier S. pr_ready + automerge_enable in this act.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 27, 2026 09:55
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 27, 2026
Merged via the queue into main with commit c68869c Oct 27, 2026
49 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-34f9ql branch October 27, 2026 10:22
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 protocol:data size/m tests tooling

Projects

None yet

2 participants