Skip to content

spec(object-timeline): items describe names objectui as the item-shape owner - #20531

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20283-timeline-items-element-owner
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-20283-timeline-items-element-owner

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #20283

Clause-②: no

What changed

ObjectTimelinePropsSchema.items (packages/spec/src/ui/component.zod.ts) stays z.array(z.unknown()) — no schema-shape change. Its .describe() no longer says "the author owns the item shape". It now names the actual owner: each element is objectui's declared timeline element, @object-ui/types's TimelineFeedItem (variant absent / vertical / horizontal) or TimelineGanttItem (variant: 'gantt'), the arm this node's own variant selects.

content/docs/references/ui/component.mdx was regenerated via gen:schema && gen:docs — never hand-edited.

.changeset/20283-object-timeline-items-element-owner-describe.md — @objectstack/spec patch.

Route taken (per the card, not re-opened)

items keeps z.array(z.unknown()); the value contract stays owned by objectui. Declaring the arms here would fork a second vocabulary. mapping and object-gantt.markers hold the same VALUE posture unchanged.

Verification record

Premise re-measure (required before touching code). At .objectui-sha dd3f7e1be3561d63267d7162f3fc0ac52e72834d (the pin this branch built against), TimelineFeedItem and TimelineGanttItem are declared as plain TypeScript interfaces in objectui's packages/types/src/data-display.ts (lines 2773 and 2834), pinned by objectui's own packages/types/src/__tests__/timeline-item-element-6356.test.ts. data-display.ts imports ChartAxis / ChartType / I18nLabel from @objectstack/spec/ui for the unrelated chart surface, but neither TimelineFeedItem nor TimelineGanttItem imports or re-declares anything from @objectstack/spec — the premise holds, so the route is followed as given (no fork to report).

Four read-point re-reads, objectui ObjectTimeline.tsx at the same pin (git show dd3f7e1be3561d63267d7162f3fc0ac52e72834d:packages/plugin-timeline/src/ObjectTimeline.tsx):

  • :254 — const hasInlineItems = Array.isArray(schema.items) && schema.items.length > 0; (loading-state seed)
  • :374 — const fetchesForItself = !!schema.objectName && !boundData && !schema.items && !(props as any).data; (fetch-skip gate)
  • :546 — if (schema.items) return schema.items; (matches the docblock's citation exactly)
  • :771 — const hasAuthoredItems = !!schema.items; (matches the docblock's citation exactly)

All four anchors in the existing docblock (packages/spec/src/ui/component.zod.ts, the object-timeline doc comment) are byte-accurate at this pin — no anchor drift, so no docblock line-number edit was needed. pnpm --filter @objectstack/spec run check:spec-docblock-symbol-anchors also confirms: 4044 anchors resolve, 0 new anchor drift.

The variant describe ("gantt needs authored items") still reads true beside the new items describe — confirmed against ObjectTimeline.tsx:809, if (!hasAuthoredItems && schema.variant === 'gantt') — left unchanged.

Full-repo pin sweep for the stale sentence: grep -rl "author owns the item shape" across the whole tree (excluding node_modules/.git) found exactly the two carriers named at dispatch — packages/spec/src/ui/component.zod.ts and the generated content/docs/references/ui/component.mdx — both fixed here. No test pins the old describe string (grep for it and for becomes a pass-through outside these two files: zero hits), so no fixture triage was needed.

Round 2 correction — a false refusal clause, measured and dropped. The first cut of this describe (and the changeset) also said objectui's strict authoring face "refuses a mismatched arm and undeclared keys" for object-timeline itself. A seat review measured that false at the pin, and this round independently re-measured it before editing: objectui's zod authoring face (AnyComponentSchema / StrictAnyComponentSchema, packages/types/src/zod/index.zod.ts) is a z.discriminatedUnion('type', …) whose member literals — read via git grep -n "z.literal('object-" dd3f7e1be3561d63267d7162f3fc0ac52e72834d -- packages/types/src/zod — are object-grid / object-form / object-view / object-map / object-tree / object-gantt / object-calendar / object-kanban / object-chart / object-gallery / object-data-table and, separately, timeline — never object-timeline. To measure the actual runtime answer rather than infer it, packages/types was exported read-only via git archive dd3f7e1be3561d63267d7162f3fc0ac52e72834d packages/types into a throwaway probe run with tsx against this repo's own built @objectstack/spec (the objectui checkout itself was never built, checked out, or stashed): StrictAnyComponentSchema.safeParse({ type: 'object-timeline', items: [{ label: 'x' }], variant: 'vertical' }) returns a single invalid_union issue, "note": "No matching discriminator", on both the tolerant and the strict face — object-timeline is refused outright as an unrecognized node type, before any per-item judgment runs. A control on a genuine { type: 'timeline', … } node reproduces the arm-selection and unrecognized_keys mechanism exactly (a gantt row's label under variant: 'vertical' draws a named custom issue; a feed item's undeclared date key draws unrecognized_keys) — that mechanism lives on the sibling timeline zod node (TimelineSchema's .superRefine), which object-timeline types against only in TypeScript (ObjectTimeline.tsx's schema: TimelineSchema & { … }), never at runtime for this node. The refusal clause is dropped from the describe and the changeset; nothing else about the route changed. Re-verified after the fix: check:generated (15/15 green), the two component/docs-facing unit test files (component.test.ts, component-element-navigation-17987.test.ts — 366 tests green), check:doc-authoring and check:spec-docblock-symbol-anchors (both green), and a fresh dispatch-gates.mjs reconciliation on the new commit — same 102 derived families as round 1 (the diff only edits words inside the same two describe strings), 99 run green, the same 3 PREREQUISITE NOT MET NOT MEASURED as round 1.

Tests run (this worktree, objectstack-issue-20283, HEAD 0197523247; round 1 ran at HEAD 3635807a7f)

  • pnpm --filter @objectstack/spec build — exit 0 (rebuilt after merging origin/main).
  • pnpm --filter @objectstack/spec check:generated — exit 0, all 15 generated artifacts up to date (pre- and post-merge).
  • pnpm --filter @objectstack/spec test — 573 test files passed (573), 16809 tests passed | 1 todo.
  • pnpm --filter @objectstack/spec typecheck (tsc --noEmit + check:scripts-typecheck + check:test-typecheck) — exit 0.
  • node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran (an annotated ran-list file) reconciliation: 102 derived families, 99 run green, 3 NOT MEASURED (PREREQUISITE NOT MET, exit 3), 0 unrun — pnpm --filter @objectstack/spec run check:skill-examples, pnpm check:dual-build-cjs-loads, pnpm check:lean-entry-closure each refused locally before measuring anything because they read BUILT output of unrelated packages (@objectstack/client-react, and — for dual-build-cjs-loads — the whole workspace's dist/, 82+ packages) that this describe-only change does not touch and that a full local build did not fit this task's floor-tier scope; the shared os-verify-lock queue was also heavily contended by sibling agents' builds throughout this run. CI's full farm covers these.
  • pnpm check:doc-authoring — exit 0: 16748 customer-facing strings across 1170 spec sources clean, no internal issue-id references (the new describe carries none).
  • pnpm check:nul-bytes, pnpm check:merge-driver, pnpm check:objectui-pin-citations, check:changeset-gate-self-tests (check:empty-changeset / check:adr-0087-registration / check:changeset-no-major) — all exit 0.
  • Dependency-closure build (①): none — packages/spec has no workspace dependencies, so the closure is empty by construction.
  • Downstream consumer closure: not run — this is a .describe() text change only; the compiled .d.ts type surface is byte-identical (confirmed by check:api-surface and check:export-origins both green with zero diff), so no downstream package's typecheck is implicated.

Acceptance notes

No out-of-scope findings of class (a)/(b)/(c) surfaced while working this card. origin/main advanced twice during this round (to fb194c70e5, then merged again to fb386074f5 after a clean git merge origin/main with no conflicts and no overlap with the two touched files); rebuilt and re-ran check:generated after the merge, still all 15 green.


Generated by Claude Code

…the item shape"

ObjectTimelinePropsSchema.items stays z.array(z.unknown()) — no schema-shape
change. Its describe now names the actual owner: each element is objectui's
declared timeline element, @object-ui/types's TimelineFeedItem (feed, variant
absent / vertical / horizontal) or TimelineGanttItem (a gantt row, variant:
'gantt'), selected by the node's own variant, whose strict authoring face
refuses a mismatched arm and any undeclared key.

Regenerated content/docs/references/ui/component.mdx via gen:schema && gen:docs.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation protocol:ui tooling labels Sep 29, 2026
@github-actions

github-actions Bot commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 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 — 137 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 03b19d9cfdb7f0811dc27398b51b44d332a2ed88 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 97e416b8cb82c2649b3a47b61eb9fcef09072d47 — the merge of head 891830acdf29765700ac43c36a38aad9348543fd into base 03b19d9cfdb7f0811dc27398b51b44d332a2ed88, 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 97e416b8cb82c2649b3a47b61eb9fcef09072d47 && git checkout 97e416b8cb82c2649b3a47b61eb9fcef09072d47
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 03b19d9cfdb7f0811dc27398b51b44d332a2ed88 891830acdf29765700ac43c36a38aad9348543fd && git checkout -B drift-repro 03b19d9cfdb7f0811dc27398b51b44d332a2ed88 && git merge --no-ff 891830acdf29765700ac43c36a38aad9348543fd

node scripts/docs-audit/affected-docs.mjs --json 03b19d9cfdb7f0811dc27398b51b44d332a2ed88

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

Round-2 correction: the previous describe claimed objectui's strict
authoring face refuses a mismatched arm and undeclared keys for
object-timeline. Measured false at the objectui pin
dd3f7e1be3561d63267d7162f3fc0ac52e72834d: objectui's zod authoring face
(AnyComponentSchema / StrictAnyComponentSchema, a z.discriminatedUnion
on `type`) has no object-timeline arm at all. A live
StrictAnyComponentSchema.safeParse({ type: 'object-timeline', items: [...] })
against objectui's built types at this pin returns invalid_union / "No
matching discriminator" on both faces -- not an arm/key judgment. The
arm-selection and unrecognized_keys mechanism lives on the sibling
`timeline` zod node (TimelineSchema's superRefine), which object-timeline
types against only in TypeScript (ObjectTimeline.tsx's
schema: TimelineSchema & { ... }), never at runtime.

items stays z.array(z.unknown()) -- no schema-shape change. The describe's
second sentence now says only the measured-true part: each element is
objectui's declared timeline element, TimelineFeedItem or TimelineGanttItem,
the arm the node's variant selects. Regenerated
content/docs/references/ui/component.mdx via gen:schema && gen:docs.
Corrected the changeset prose the same way and recorded the correction in it.

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

The "Correction (round 2): ..." paragraph referenced round 1's wording,
which never shipped and never left this branch -- the published
CHANGELOG has no reader who saw it, so a paragraph explaining "the
first cut of this describe" tells them about something that never
happened to them. That record already lives in the PR body's
verification section. The changeset keeps only its first two
paragraphs (the actual change, and why it is documentation-only).

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 891830acdf29765700ac43c36a38aad9348543fd
Local-runs: none

Inputs, and nothing else: card #20283 (body; triage grade 5859517446, unlock 5879567964, claim 5880073803, os-dev-reports 5881158750 / 5881621744 / 5881658978); PR #20531 (body, its one comment 5881140085, file list, and the net diff against origin/main 03b19d9cfdb7, which is also the base the head's CI merge used); the 32 check-runs on the head, read once; and the objectui pin .objectui-sha dd3f7e1be3561d63267d7162f3fc0ac52e72834d, read as git objects. Nothing built, run or re-run; no worktree.

① Derived judgments

Net diff: three files, +9 / -2 — one .describe() string, its generated mirror, one changeset. Head repo is the base repo (not a fork). No governed-surface path in the file list. The file surface equals the claim's exactly; no objectui edit; the four branch commits net to exactly the PR's file list.

  1. Accept set of items — UNCHANGED, right. items: z.array(z.unknown()).optional() is byte-identical before and after (packages/spec/src/ui/component.zod.ts:5209); only the describe string moved. That is the claim's route and the file's VALUE posture (:3285-3290: the keys are the spec's, the value contract still lives in objectui; mapping at :5223 holds the same way). No key, enum member, union arm, export or registration is added anywhere in the diff.
  2. Public surface — UNCHANGED, right. No export added, removed or renamed; api-surface/ untouched; a describe-only edit cannot move the compiled .d.ts.
  3. Every clause of the new describe, read against the pin, not the dev's prose:
    • "objectui's declared timeline element" — TRUE. packages/types/src/data-display.ts:2894 ("The element is declared: two arms, and variant picks one"); items is an array of TimelineFeedItem or TimelineGanttItem at :2932.
    • "@object-ui/types's TimelineFeedItem … or TimelineGanttItem" — TRUE. Package name @object-ui/types (packages/types/package.json); both interfaces declared at data-display.ts:2773 and :2834, exported from packages/types/src/index.ts:261-262.
    • feed arm = node variant absent / vertical / horizontal; gantt arm = variant: 'gantt' — TRUE. data-display.ts:2899-2902; the zod mirror's TIMELINE_ITEM_ARMS (packages/types/src/zod/data-display.zod.ts:1201-1216) and its refinement's "absent ⇒ vertical" (:1219-1220); objectui's own pin timeline-item-element-6356.test.ts:161 ("absent reads as vertical").
    • "the arm this node's variant selects", for an object-timeline node — TRUE. ObjectTimelineProps.schema is TimelineSchema intersected with the object-bound keys (packages/plugin-timeline/src/ObjectTimeline.tsx:165-219), and the intersection redeclares neither items nor variant, so the node's element type and its three-value variant are TimelineSchema's own. Authored items pass through untouched (:546, :771) into effectiveSchema.items (:874) and on to TimelineRenderer (:942), whose branch is chosen by variant (renderer.tsx:1419 horizontal, :1455 gantt, vertical otherwise) and reads only that arm's keys. ObjectTimelinePropsSchema.variant here is the same three values (:5211).
    • "the renderer becomes a pass-through" — TRUE: :546 if (schema.items) return schema.items;. The retired sentence "the author owns the item shape" is gone from every carrier at the head (git grep over the whole tree: only the changeset quotes it, as the text being retired).
    • The round-1 refusal clause ("refuses a mismatched arm and undeclared keys") — GONE from the describe, from component.mdx, from the changeset and from the PR body's "What changed" paragraph; it survives only in the body's verification record as history, which is the right place. The removal is right, confirmed statically: git grep object-timeline over packages/types/src/zod at the pin returns nothing; the component union's object-* literals are grid / form / view / map / tree / gantt / calendar / kanban / chart / gallery / data-table, plus the sibling timeline. No runtime face judges an object-timeline node's items, so a refusal clause would have advertised enforcement that does not exist (Prime Directive chore: version packages #10). Round 1's clause was false; the dev measured it and removed it.
    • Non-blocking observation: TimelineFeedItem carries its own variant key, a marker colour (TimelineItemVariant = default / success / warning / danger / info, data-display.ts:2751, :2785). The parenthetical after TimelineFeedItem names the NODE's variant values, and the sentence's closing clause ("the arm this node's variant selects") is what fixes that reading; TimelineGanttItem has no variant key at all, so the gantt parenthetical admits no other reading. Not a false clause. A later edit that drops the closing clause would make it one.
  4. No second vocabulary — right. The describe names two types by name and the node's own three variant values (already this schema's enum one line down); no key list (time, title, label, …) is copied. Consistent with triage 5859517446 and the claim's ⛔.
  5. component.mdx is the generator's output — right. Row template | **key** | type | required | desc | (packages/spec/scripts/lib/schema-section.ts:544); desc is escapeMdxDescription(description + externalVocabularyNote + dimensionlessNote) then backslash and pipe escaping (:536-543). The new describe carries no brace, no angle bracket, no pipe, no backslash and no newline, and items declares neither externalVocabulary nor dimensionless (both notes read schema metadata, never prose), so the cell is the describe byte-for-byte — compared at the head: 383 bytes each, identical. The type cell any[] and the optional cell are unchanged because the shape is. Exactly one generated carrier exists (json-schema/ is gitignored). CI's own answer, check:docs in Type Check · source gates: success on this head.
  6. Docblock anchors — right. The object-timeline docblock's items read points (:254, :374, :546, :771) are byte-accurate at the pin; :809 (if (!hasAuthoredItems && schema.variant === 'gantt')) still holds, so the neighbouring variant describe ("gantt needs authored items") stays true beside the new one. No anchor edit was needed and none was made.
  7. Premise (no fork) — holds. data-display.ts at the pin imports only ChartAxis / ChartType / I18nLabel from @objectstack/spec/ui (:18); both timeline interfaces are declared locally, and nothing in this repo imports them.

Gate coverage — the 32 check-runs on the head, read once at judgment:

  • Concluded success (13): Type Check · source gates (carries check:docs, the mdx parity above; check:authorable-surface; check:generated --reconcile-only; check:export-origins; check:objectui-pin-citations), Check Changeset, Governed Surface Queue Guard, Spec property liveness, Check Documentation Links, Flag docs affected by code changes (its comment 5881140085: nothing to list, computed on the merge of this head into 03b19d9cfdb7), Check PR Size, Auto Label, filter, and the four PR/card consistency checks (The card this PR closes must claim this branch, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Part-of PR must not also close its card).
  • Not concluded at the read (in_progress, 17) — named, not presumed green: Lint & Repo Gates (carries pnpm lint, check:doc-authoring, check:spec-docblock-symbol-anchors, check:changeset-gate-self-tests), Type Check · workspace, Type Check · consumer gates (check:api-surface, check:exported-any, check:skill-examples), Type Check · debt ledger, Test Core (1/6) through (6/6) (the spec unit tests ride the affected shard), Build Core (check:dual-build-cjs-loads, check:lean-entry-closure), Build Docs (the MDX compile), Dogfood Regression Gate (1/3) through (3/3), Dogfood Verify CLI, Temporal Conformance (live PG + MySQL).
  • Skipped by design (2): Console Pin Gate (the filter job's console output is false — the diff touches neither the pin nor the console), Packed-tarball smoke (opt-in).
  • Of the required seven contexts: one concluded success (Governed Surface Queue Guard); three in progress (Lint & Repo Gates, Build Core, Temporal Conformance (live PG + MySQL)); three aggregates had no check-run on the head yet at the read (TypeScript Type Check, Test Core, Dogfood Regression Gate) — their lanes and shards are the in-progress runs named above.
  • Failures: none at the read, so nothing to attribute to this diff. Every derived family I could map to a workflow step has a check-run on this head; none is CI-less. One judgment has no CI family at all and is answered only here: the truth of the describe against the objectui pin — a prose claim about a sibling repo's types, which check:spec-docblock-symbol-anchors (line anchors) does not read.

② Semver level

@objectstack/spec: patch — right. A .describe() string is runtime metadata that ships in the built package and in the generated JSON Schema and reference docs, so the diff publishes; skip-changeset would be wrong. Nothing widens or narrows, so minor and BREAKING are wrong. Clause-②: no (PR body; claim 5880073803) — right: none of the four widening tells (new key, new closed-set member, new export row, new registration) appears in the added lines, and the accept set of items is the same universal z.unknown() before and after. The changeset says only what ships: paragraph one the correction, paragraph two why it is documentation-only and not value-tightening; the round-2 "Correction" paragraph is gone (round-3 commit 891830acdf), so no unshipped history reaches CHANGELOG.md. Check Changeset: success on this head.

③ Boundary flags

  • open_questions: [] in rounds 1, 2 and 3 — nothing to answer, nothing to escalate.
  • out_of_scope_findings: [] in all three rounds.
  • Round-2 deviations, each answered:
    1. Measuring the refusal claim by a git archive export of objectui's packages/types at the pin plus a tsx probe against this repo's built spec — a READ of objectui (no checkout, edit or stash), inside the claim's ⛔ (no objectui edit). Its finding (no object-timeline arm) is confirmed here statically — ACCEPTED.
    2. Container restart mid-round-2, resumed from the surviving worktree diff and re-verified — the head's own check-runs re-derive every family; ACCEPTED.
    3. Worktree recreated for round 2 with the prerequisite builds re-run — process cost only; ACCEPTED.
  • The dev's three NOT MEASURED families (check:skill-examples, check:dual-build-cjs-loads, check:lean-entry-closure): each has a CI carrier on this head (Type Check · consumer gates; Build Core for the other two), all pending at the read — not escalated; the owning seat reads their conclusions before enqueue.
  • The claim's "stop on breach": no breach reported, none found.
  • Nothing escalated.

Implemented-by: claude/issue-20283-timeline-items-element-owner
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 01:27
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit 7e36a3c Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20283-timeline-items-element-owner branch September 29, 2026 01:50
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… commits that decided them (stage 4) (objectstack-ai#20548)

Part of objectstack-ai#20234
Clause-②: no

## What changed

This is stage 4 of the staged sweep: the `data/` remainder. It covers
the six `packages/spec/src/data/` files stage 3 (PR objectstack-ai#20533, landed
`03b19d9cfd`) left out because an open PR held them, and nothing else.
They are `object.zod.ts`, `filter-logic-conformance.ts`,
`object.form.ts`, `data-engine.zod.ts`, `data-engine.test.ts` and
`hook.form.ts`. Later stages cover the other areas, so this PR says
`Part of`.

The census below measured all six. Three of them carry comment or
docblock sites that cite a tracker number answering 404.
`data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts` carry
none, so they are not in the diff.

Every such site has been rewritten in ruling C+D's form C (comment
5749154545 on objectstack-ai#19123). That is **19 sites on 19 lines in 3 files,
covering 9 numbers**. Each rewritten line now cites the commit in
`origin/main` history that decided what the line describes, and it says
in its own words what that commit decided. Where a PR number was already
on the line (`PR objectstack-ai#13529`), it stays beside the commit as the link.

No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/`
records the decision behind any of the 9 numbers: a search for each
number, with and without `#`, finds nothing there. So every anchor is a
commit: **9 distinct shas**. Stage 3 had already read these commits and
recorded them in PR objectstack-ai#20533's body. They were not copied from there. Each
one was re-read against the current line it anchors: its own message or
diff names the number it replaces, and it made the change the line
describes. `object.zod.ts` and `filter-logic-conformance.ts` moved on
`main` after stage 3 read them (PRs objectstack-ai#20521 and objectstack-ai#20523). Each site was
therefore re-read at this base, `03b19d9cfd`.

Only comments changed. Every source file keeps its line count (20 lines
out, 20 in, over 3 files), so no line citation into these files moves.
One of the 20 lines held no dead citation:
`filter-logic-conformance.ts:249`, the first half of a sentence reflowed
onto `:250`. No code token moves (see the guard below).

**No tracker number is added.** Every tracker number on an added line
was already in the hunk it replaces. `PR objectstack-ai#13529` stands on three added
lines, and on the three removed lines of the same hunks. It is the link
beside commit `9dac1ae01`, which stage 3 recorded the same way.

No reference page under `content/docs/references/` moved: none of the
rewritten docblocks projects into one (`check:docs` at the head: `226
generated files in sync`). The PR adds one `patch` changeset for
`@objectstack/spec` (see Changeset below).

## Census: the six files, before and after

**Instrument.** This is the instrument of stages 1 to 3. It sends REST
`GET /repos/objectstack-ai/objectstack/issues/N` without following
redirects, for every distinct number cited in `packages/spec/src/data`.
The population is:
- the citation gate's own exported `CITATION_RE` and
`NON_CITATION_HEADS`, kept when the qualifier is none, `objectstack`,
`objectstack-ai/objectstack`, `framework`, `pre-` or `post-`;
- widened case-insensitively to `Pre-`, `POST-` and `Framework`, as in
stage 3;
- N of 100 or more, excluding `summon` heads.

Each site is classified by the TypeScript parser as a line comment, a
docblock, a block comment or a string.

Two cross-checks close the population. First, a raw `#N` count in each
of the six files equals the census rows plus the cross-repo rows in five
files. In the other two it is one higher, and the extra is a second
number after a slash inside a string (`objectstack-ai#5322/objectstack-ai#5134` in a `note`,
`objectstack-ai#6262/objectstack-ai#6433` in a test title). Both answer 200. Second, no spelled
citation (`issue N`, `PR N`, `card N`) occurs in any of the six.

**Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The
dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at
the start, after every 100 numbers and at the end: 24 of 24 lit (200)
and 24 of 24 dead (404) over 8 checkpoints in the base run, and 21 of 21
lit and 21 of 21 dead over 7 checkpoints in the head run.

| reading | tree | numbers probed | 200 | 404 | 301 or other | dead
sites, all of `data/` | dead sites, the six files | lines | files |
numbers |
|---|---|---|---|---|---|---|---|---|---|---|
| before | base `03b19d9cfd`, probed 2026-09-29T01:11:59Z to 01:15:49Z |
601 | 572 | 29 | 0 | **77** | 19 | 19 | 3 | 9 |
| after | head `53c9070dfd`, probed 2026-09-29T01:25:55Z to 01:29:35Z |
597 | 572 | 25 | 0 | **58** | 0 | 0 | 0 | 0 |

The head probe found no number newly dead since the base probe: the same
572 numbers answer 200. The base reading of 77 equals stage 3's after
reading at `96fd49caa2`.

**Per file.** Cited sites here are every in-repo citation the population
reads, live or dead.

| file | cited sites (base) | dead sites before | by class | dead sites
after |
|---|---|---|---|---|
| `object.zod.ts` | 120 | 15 | 8 docblock, 7 line comment | 0 |
| `filter-logic-conformance.ts` | 97 | 3 | 2 docblock, 1 line comment |
0 |
| `object.form.ts` | 31 | 1 | 1 line comment | 0 |
| `data-engine.zod.ts` | 48 | 0 | | 0 |
| `data-engine.test.ts` | 29 | 0 | | 0 |
| `hook.form.ts` | 0 | 0 | | 0 |

None of the 19 sites is a string, so this stage leaves no string token
behind.

## Per-number table

| number | sites / lines | anchor: what it decided |
|---|---|---|
| `objectstack-ai#8772` | 4 / 4, `object.zod.ts:2718`, `:2731`, `:2744`, `:2910` |
`75b7c240a`: Direction 2 of the 2026-08-16 maintainer ruling.
`ObjectSchema.create()` forces `required: true` on a `master_detail`
reference under `controlled_by_parent` and refuses an explicit
`required: false`. Raw parse stays tolerant, and runtime tolerance is
the ruling's other half. Its changeset records the measurement that only
the security gate closed that shape while the declaration surface
accepted it (`:2731`). ADR-0055 stays cited beside it. It is the same
anchor stage 3 gave `object.test.ts` |
| `objectstack-ai#10165` | 2 / 2, `object.zod.ts:818`, `:1036` | `801296050`:
`ttl.onlyWhen` with the canonical null predicate (maintainer ruling
2026-08-20, option A). One shared `onlyWhen` union, and both of
`retention.onlyWhen`'s conflicts mirrored. Its diff wrote both
`[objectstack-ai#10165]` blocks |
| `objectstack-ai#10347` | 3 / 3, `object.zod.ts:1006`, `:1042`, `:1049` |
`530c1df65`: the Archiver honours a declared `ttl`. It selects by the
ttl cutoff on `ttl.field` when `ttl` is declared, and by `created_at` /
`archive.after` otherwise (maintainer ruling 2026-08-20) |
| `objectstack-ai#10527` | 1 / 1, `object.zod.ts:1005` | `5649efbf9`: refuses a
diverging retention + ttl + archive triple at parse time. Its diff wrote
this very paragraph |
| `objectstack-ai#11195` | 1 / 1, `object.zod.ts:1791` | `b37231883`:
`UserActionsConfigSchema` adopts `group` / `hideFields` / `rowColor`
(the "last three" the line names) |
| `objectstack-ai#11408` | 1 / 1, `object.zod.ts:2189` | `f11fc61c5`: declares
`editMode` on the object document (maintainer ruling 2026-08-24, the
`objectstack-ai#10144` declare-or-rule-out family, which stays cited) |
| `objectstack-ai#13608` | 3 / 3, `object.zod.ts:2317`, `:2354`, `:2366` |
`fc9ba76a5`: `publicSharing.eligibility` is held at redemption, not only
at mint, fail-closed, with the undifferentiated `null` refusal. Its
changeset heads with objectstack-ai#13608. It is the same anchor stage 1 gave
`contracts/share-link-service.ts` |
| `objectstack-ai#13195` | 3 / 3, `filter-logic-conformance.ts:190`, `:250`, `:525` |
`9dac1ae01`, PR objectstack-ai#13529's squash commit, which stays as the link:
`$exists` means has-a-value on driver-memory's live mingo path, its
analytics face and driver-mongodb's `translateFilter` (the "last three
key-presence exits") |
| `objectstack-ai#12868` | 1 / 1, `object.form.ts:256` | `c459da6bc`: narrows the
per-option `default` key out of the form-view options vocabulary, which
offered a key nothing on that surface read. Commit `e808890958`, which
wrote this line, names objectstack-ai#12868 as the same offer-vs-door class |

The shas were checked at the base and again at `origin/main`
`288611e3e5`. Every one matches exactly one commit (`git rev-parse
--disambiguate`, count 1). Every one is an ancestor (`git merge-base
--is-ancestor`, exit 0 for 9 of 9). The control leg `e9584681a4` also
exits 0, and the repository is not shallow. For each commit, a grep of
its own message or diff finds the number it replaces. Seven of the nine
name it in the message. `fc9ba76a5` names it in its diff (20 lines,
including its changeset heading), and so does `c459da6bc` (8 lines,
including its changeset heading).

Wordings to check, each true of its commit:
- `object.zod.ts:2731` now reads 「closes that shape, and commit
75b7c24 records that the declaration and the enforcement disagree」.
The measurement was the card's. The commit's changeset records it: "only
the security gate closed that shape while the declaration surface
accepted it".
- `object.zod.ts:2189` reads 「Declared here by commit f11fc61's
maintainer ruling」, and `:2744` reads 「the other half of commit
75b7c24's ruling」. This is stage 3's wording for the same relation
(`object.test.ts`, 「the other half of commit 75b7c24's ruling」): the
commit that landed the ruling and quotes it.
- `object.zod.ts:1049` reads 「That is the whole of what [commit
530c1df] changed here」. Commit `52db1d1f2a` wrote the paragraph.
`530c1df65` is the change it describes.

## Mechanical guard: no code token moves

The check compares leaf tokens with comments stripped, base `03b19d9cfd`
against head `53c9070dfd`. It uses the TypeScript parser's leaf tokens
(TypeScript from the head's lockfile), so template literals are scanned
in context, and it excludes JSDoc nodes. It ran over all 3 touched `.ts`
files. It is the stage-3 instrument, unchanged.

- Real run: 13,624 base tokens (object.zod.ts 8,774, object.form.ts
3,226, filter-logic-conformance.ts 1,624), **0 files with a token
change** (exit 0).
- Comment-insertion control (`object.form.ts`): 0 files changed, as
expected (exit 0).
- Positive control (a declaration inserted into `object.zod.ts`): 1 file
reads DIFFER at token 1629 (exit 1).
- Positive control (one digit changed inside the `objectstack-ai#5322/objectstack-ai#5134` `note`
string in `filter-logic-conformance.ts`): 1 file reads DIFFER at token
889 (exit 1).

Line balance: `object.zod.ts` +15 / -15, `filter-logic-conformance.ts`
+4 / -4, `object.form.ts` +1 / -1. Line counts are equal at base and
head: 3,240, 621 and 751.

## Changeset

This change ships bytes, so a `patch` changeset for `@objectstack/spec`
is included. It says only that the provenance comments were re-anchored.
`Clause-②: no`: no export, key, value or type moves (the guard above).

Measured on the head's built package: `object.zod.ts` is
`src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten
comments also reach `dist`:
- `9dac1ae01` appears in `dist/data/index.d.ts` (the
`filter-logic-conformance.ts` docblock) and in 4 bundled `.js` files;
- `fc9ba76a5`, `f11fc61c5` and `b37231883` each appear in 22 bundled
`.js` files, and `c459da6bc` in 12;
- the positive control, the pre-existing `object.zod.ts` sentence
「Fail-CLOSED at both points」, appears in 11 bundled `.js` files.

## Gates (head `53c9070dfd`)

- **Citation judging pass, run as CI runs it:** `pnpm
check:issue-citations && node scripts/check-issue-citations.mjs` exits
0. The self-test passes 73 cases in 7 batteries. The live run judged 6
citations across 3 files: 3 resolve (`objectstack-ai#9138` twice, `objectstack-ai#11410`) and 3
resolve as a pull request (`objectstack-ai#13529`, the link).
- **Doc authoring:** `pnpm check:doc-authoring` exits 0.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at the head derived 79 families, and
all 79 exit 0. `--ran` reports 79 run, 0 NOT MEASURED, 0 unrun, and
exits 0. A full `turbo run build` of `./packages/*` ran first, under the
shared verify lock: 71 of 71 tasks, VERDICT command-exit 0. So no gate
met an unbuilt prerequisite.
- `pnpm --filter @objectstack/spec run check:generated`: under the lock
against that build, `All 15 generated artifacts are up to date`, VERDICT
command-exit 0.
- **Tests and typecheck:**
- `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2
src/data` under the lock: Test Files 107 passed (107), Tests 3527
passed, 1 todo (3528), VERDICT command-exit 0. It covers every test in
`data/`, among them `object.test.ts`, which reads these schemas.
- The 13 spec suites outside `src/data` that read the touched files'
source text or pin their line numbers, under the lock: Test Files 13
passed (13), Tests 544 passed (544). They are stage 3's 12
(`scripts/{file-description,root-index,skill-map-guards,strictness-ledger}.test.ts`,
`src/api/api-entry-graph.pin.test.ts`,
`src/contracts/scoped-context.test.ts`,
`src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence}.test.ts`,
`src/system/constants/platform-object-names.test.ts`,
`src/type-alias-convention.pin.test.ts`, `src/ui/dashboard.test.ts`)
plus `src/shared/union-author-message-pins.test.ts`, which pins
`data/object.zod.ts:855`.
- `pnpm --filter @objectstack/spec typecheck` under the lock exits 0,
including `check:test-typecheck` (53 files, 251 errors, 138 pinned
signatures held).
- **Lint, as a proven narrowing at the head:** `eslint
--no-inline-config --format json` over the 3 touched `.ts` files gives 3
files, 0 errors and 0 warnings. All 3 are in eslint's own population
(`isPathIgnored` is false for each). `eslint.config.mjs` never enables
type-aware linting (no `parserOptions.project`, which its own line 328
states), so a comment edit here cannot move the verdict on any untouched
file. The repo-wide `pnpm lint` is CI's run.

## Acceptance notes

- **Base.** The branch forked from `03b19d9cfd`, stage 3's landing.
`origin/main` then moved two commits (`05077d4c26`, PR objectstack-ai#20532, and
`288611e3e5`, PR objectstack-ai#20536), and neither touches `data/`. `dispatch-gates`
flagged its derivation as stale because `scripts/regen-artifacts.mjs`
had moved, so `origin/main` was merged in (`53c9070dfd`, a clean merge
with no driver-deferred path) before the gates ran. The PR's delta
against `origin/main` is exactly its 4 files. `origin/main` has since
moved two more commits: `7e36a3cd7c` (PR objectstack-ai#20531) and `ba5927f714` (PR
objectstack-ai#20460). Neither touches `data/` or anything the gate derivation reads,
and a re-derivation prints the same 79 commands. A no-driver
`merge-tree` of the head onto `ba5927f714`, from a bare shared clone,
exits 0. So there is no second merge.
- **Open PRs, re-read at 2026-09-29T02:01Z:** 9 open PRs, and none
touches any of the six files. The `data/` files open PRs touch are
objectstack-ai#20458's `analytics*` files, objectstack-ai#20504's `driver/turso.*`, and objectstack-ai#20545's
`filter-number-comparand-declared-type.*`, which is disjoint. Since the
claim, PR objectstack-ai#20460 has landed (`ba5927f714`) without touching
`filter-subtree-provenance.ts`. That file's 3 dead sites are outside
this claim's fence, so they are left for a later stage.
- **The rung.** Two anchored changes also have ADR-0087 entries in
`packages/spec/src/migrations`: `cbp-master-detail-required-forced` for
objectstack-ai#8772, and `form-view-option-default-retired` for objectstack-ai#12868. The second
entry's own header names commit `c459da6bc`. This PR takes the commit
rung, as stages 1 to 3 did. The D3 id is the more durable in-repo
record, if the ruling's first rung is later read to include those
entries.
- **What stays in `data/` after this stage: 58 dead sites.**
- **12 comment sites in files other open work still holds.**
`analytics.zod.ts`, `analytics-strictness-batchd.test.ts` and
`analytics-date-range-two-bound-window.test.ts` hold 5 (objectstack-ai#20300, PR
objectstack-ai#20458). `driver/turso.zod.ts` and `driver/turso.test.ts` hold 4
(objectstack-ai#20437, PR objectstack-ai#20504). `filter-subtree-provenance.ts` holds 3. It was held
by objectstack-ai#20367 and is now free (see above).
- **3 comment sites stage 3 left on purpose.** They are the test-read
`[objectstack-ai#6259]` marker at `api-derivation.ts:163`, the test comment at
`api-derivation.test.ts:232` that names it, and `field.zod.ts:370`,
whose `objectstack-ai#6111` is objectui's number.
- **43 string sites**, left as tokens: 41 test strings (2 of them in the
held analytics and turso test files) and the 2 exported
`AGGREGATION_CASES` note strings in `aggregation-conformance.ts`
(`:398`, `:407`, objectstack-ai#11065), which objectstack-ai#20489's claim holds.
- **Outside `data/`,** the card's other remaining items are unchanged:
the migrations and ui areas, the `liveness/**` notes, the `why` strings,
the `PROVENANCE_WAIVERS` reason, and `rest-server.zod.ts`.
- **The citation gate's reach.** It defers `packages/**/*.test.ts`. No
test file is touched here, so all 3 touched files are in its judging
population.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
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:ui size/s tooling

Projects

None yet

2 participants