Skip to content

fix(service-settings): the settings audit trail records secret-valued settings with the keyed digest - #21809

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21792-settings-audit-digest
Oct 5, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-21792-settings-audit-digest

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21792

Clause-②: no

What changes

The settings audit trail now records secret-valued settings (encrypted keys) with the platform's keyed digest (ICryptoProvider.keyedDigest), never the unkeyed digest. This follows the maintainer ruling recorded on the card. Non-secret settings are unchanged.

  • packages/services/service-settings/src/settings-service.ts: one private helper, secretAuditDigest, supplies the fingerprint for a secret-valued write on both write branches (the sys_secret path and the legacy inline-adapter path). Both ledgers take it: the sys_audit_log config_change row (valueDigest, now spelled with an hmac-sha256: digest inside the existing encrypted marker) and the sys_setting_audit row (newHash).
  • No keyed digest available. This happens when no crypto provider is wired (a host that builds the service with only a CryptoAdapter) or when the provider refuses. The rows then record the write with no fingerprint: valueDigest is the bare encrypted marker and newHash is null. A warn is logged once per key. The write itself is never refused, matching the existing rule that a ledger never fails a settings save. The unkeyed digest is never used as a fallback.
  • packages/spec/src/contracts/crypto-provider.ts: the contract text is revised as the ruling asks. digest is the audit fingerprint of non-secret values only. keyedDigest requirement 3 now says a secret's audit fingerprint comes from it and never from digest, and that with no keyed digest the trail records none. Comment-only: no type, export or schema change.
  • Changeset: @objectstack/service-settings patch and @objectstack/spec patch (the doc text ships in the .d.ts).

Consumers that compare these digests

No non-test code reads or compares valueDigest / new_hash / old_hash: git grep over packages/** returned only the writers, the object definition and comments. Equal-value detection still works for a reader, because the keyed digest is stable for equal input under one key. Three existing tests pinned the old unkeyed spelling for a secret. They were updated, not deleted: two now expect the keyed shape, and one (the legacy adapter, no provider) now expects no fingerprint.

Tests (at f34f69594c; test code is unchanged since fc5c86e1c6)

  • New settings-audit-secret-digest.test.ts (7 cases). For a secret, both ledgers carry provider.keyedDigest(value), which is neither provider.digest(value) nor the plain SHA-256 of the value. The fingerprint is stable for equal values and distinct for different ones. A reset records null. The legacy branch with a provider records the keyed digest. The legacy branch with no provider records no fingerprint, warns once, and the write still lands. A provider that refuses still lets the write land. For a non-secret value, both ledgers keep the adapter digest of the canonical JSON, unchanged.
  • pnpm --filter @objectstack/service-settings test: 34 files, 612 tests passed. typecheck (tsc --noEmit, include: ["src"], so it covers the tests) passed.
  • Ablation, two legs, done with scripts/ablation-replace.mjs. The tests import src, so no dist rebuild was involved. Leg 1 put back the unkeyed digest on the sys_secret branch: 2 of 7 failed (the keyed-digest case and the refusing-provider case). Leg 2 put back the adapter digest on the legacy branch: 2 of 7 failed (both legacy cases). Each leg restored to the HEAD blob (a148f503e350) with git diff HEAD empty.
  • Derived gate families (dispatch-gates.mjs --commands, 87): 85 exit 0. check:dual-build-cjs-loads exited 3 (PREREQUISITE NOT MET: it needs a whole-repo build). check:type-check-debt was killed twice by the foreground cap. Both are NOT MEASURED and left to CI. --ran reconciliation: 87 accounted, 0 unrun.
  • Lint, narrowed to the 5 touched .ts files: eslint --no-inline-config --format json reported 5 files, 0 errors, 0 warnings. The config's TS block matches **/*.{ts,…}, and type-aware linting is not enabled (no parserOptions.project), so this diff cannot change any verdict on an untouched file.

Acceptance notes

  • Optional oldHash (not done). The sys_setting_audit writer still hard-codes oldHash: null. Recording the previous fingerprint of a secret would mean decrypting the prior ciphertext on every write, so it is not the small rider the dispatch allowed. It is left for a follow-up.
  • The sys_setting_audit object's old_hash / new_hash field descriptions in platform-objects still say "SHA-256 of the … value". That was already inexact for the default adapter, and it is outside this card's file surface. Carrier: none named.
  • Audit rows written before this release keep their old fingerprint. Rotating the data key changes every later secret fingerprint.

Generated by Claude Code

claude added 3 commits October 5, 2026 03:07
… settings with the provider's keyed digest

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zT8d8NpiQ1ExhuNd5TxY6
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zT8d8NpiQ1ExhuNd5TxY6
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-settings, @objectstack/spec, touching 5 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/services/service-settings/src/sys-secret-orphan-report.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/data-modeling/drivers.mdx (via SettingsService (symbol, a top-level class))
  • content/docs/data-modeling/external-datasources.mdx (via ICryptoProvider (symbol, a top-level interface))
  • content/docs/data-modeling/validation-rules.mdx (via ICryptoProvider (symbol, a top-level interface))
  • content/docs/kernel/runtime-services/settings-service.mdx (via setMany (symbol, a method of class SettingsService))
  • content/docs/protocol/kernel/config-resolution.mdx (via ICryptoProvider (symbol, a top-level interface), SettingsService (symbol, a top-level class), setMany (symbol, a method of class SettingsService))
  • content/docs/protocol/kernel/index.mdx (via SettingsService (symbol, a top-level class))

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

  • content/docs/releases/implementation-status.mdx (via ICryptoProvider (symbol, a top-level interface))
  • content/docs/releases/v14.mdx (via setMany (symbol, a method of class SettingsService))

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
  • 1 changed file(s) yielded no anchor (packages/services/service-settings/src/sys-secret-orphan-report.ts) — pages documenting those are invisible to this run
  • 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 — 140 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 75ddcd1b41e74823b7bb9f3fe9e159a189fa2a91 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 425deaaa0db9de89126accf04e6314a3d9a33480 — the merge of head 7f7487c65bf97e2ddf3d7a2e8612c5c3de53c6a6 into base 75ddcd1b41e74823b7bb9f3fe9e159a189fa2a91, 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 425deaaa0db9de89126accf04e6314a3d9a33480 && git checkout 425deaaa0db9de89126accf04e6314a3d9a33480
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 75ddcd1b41e74823b7bb9f3fe9e159a189fa2a91 7f7487c65bf97e2ddf3d7a2e8612c5c3de53c6a6 && git checkout -B drift-repro 75ddcd1b41e74823b7bb9f3fe9e159a189fa2a91 && git merge --no-ff 7f7487c65bf97e2ddf3d7a2e8612c5c3de53c6a6

node scripts/docs-audit/affected-docs.mjs --json 75ddcd1b41e74823b7bb9f3fe9e159a189fa2a91

⚠️ 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 75ddcd1b41e74823b7bb9f3fe9e159a189fa2a91 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…secret-valued keys

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018zT8d8NpiQ1ExhuNd5TxY6
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 5, 2026 04:37
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 5, 2026 04:37
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 5, 2026
Merged via the queue into main with commit ba57588 Oct 5, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21792-settings-audit-digest branch October 5, 2026 05:09
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…ui#11636, objectstack-ai#11637, objectstack-ai#11635, objectstack-ai#11624 and objectstack-ai#11640) (objectstack-ai#21827)

Fixes objectstack-ai#21807
Clause-②: no

This moves the bundled Console's objectui pin from `9dfaca654311`
(objectstack-ai#21772, PR objectstack-ai#21800) to `0abd4f9f8769fc4c19ad2f96707684876f74c09f`, which
was objectui `main` at write time and is past `39a3e91fad`. The Console
now carries objectui#11636, the fix for objectui#11092: the screen-flow
runner names the flow by its served label, translated. That merge is
what objectstack-ai#20318 waits on. The range also carries objectui#11637
(objectui#11170), objectui#11635 (objectui#11095), objectui#11624
(objectui#11396) and objectui#11640 (objectui#11628).

⛔ This is not a release act. Version PR objectstack-ai#21352 is untouched, and no tag,
publish or Release was made.

## Range

- objectui `git ls-remote origin refs/heads/main` read
`0abd4f9f8769fc4c19ad2f96707684876f74c09f` at 2026-10-05T04:53:29Z, just
before the bump.
- Re-read at 05:37:53Z, it reads `59917c4b2` (objectui#11639, a served
view's toolbar change written inside `config`), one commit past the pin.
This PR does not carry it.
- The objectui clone is full (`--is-shallow-repository`: false). `git
merge-base --is-ancestor` exits 0 against `0abd4f9f8` for `39a3e91fa`,
`c4c506b9e`, `22ddcd5c5` and `1c2e2c46c`.
- `9dfaca654311..0abd4f9f8769` has 5 commits and 0 merges.
- objectui declared 5 changesets over the range: 3 release and 2 release
nothing. Every commit carries a changeset. None declares `major`, and
none carries the breaking annotation. The highest declared level is
`minor`, so the console changeset is `minor`. These counts come from the
bump's own digest and were re-read from the changeset blobs.
- **No commit subject in the range carries `!`.** `git log --format='%h
%s' 9dfaca654..0abd4f9f8 | grep -E '^[0-9a-f]+ [a-z]+(\([^)]*\))?!:'`
matches nothing (exit 1).

| objectui commit | landing | changeset | note |
|---|---|---|---|
| `1c2e2c46c` | objectui#11624 (objectui#11396):
`MasterDetailDetailConfig` is derived from the spec's `details` entry by
reference, member for member the same | release-nothing | moves the
manifest (below) |
| `22ddcd5c5` | objectui#11635 (objectui#11095): a dataset-bound KPI
tile's re-read on the data-invalidation bus gets a pin; tests and a doc
comment only | release-nothing | |
| `39a3e91fa` | objectui#11636 (objectui#11092): the flow runner names
the flow by `flows.FLOW.label`, then the served `flowLabel`, then the
API name | minor (`Clause-②: yes (widening)`) | smoke below; unlocks
objectstack-ai#20318 |
| `c4c506b9e` | objectui#11637 (objectui#11170): one declaration of
per-type NODE SLOTS; `objectui check`, core `validateSchema`, the SDUI
parser's `validateTree` and the `kind:'html'` compile walk them | minor
(`Clause-②: yes (narrowing)`) | answered below |
| `0abd4f9f8` | objectui#11640 (objectui#11628): the External Datasource
panel unwraps the `{ success, data }` envelope | patch | smoke below |

## Declared-breaking entries and the ADR-0087 disposition

**There are none to dispose of.** No commit subject carries `!`, no
changeset declares `major` or the breaking annotation, and the bump
wrote no `adr-0087: TODO` placeholder. `check-adr-0087-registration
--base origin/main` reports "this PR adds no declared-breaking
changeset". `check-changeset-no-major --base origin/main` reports "This
diff introduces no `major` bump".

One entry narrows by its own declaration, so here is how this repo
answers it. **objectui#11170 (`c4c506b9e`, `Clause-②: yes
(narrowing)`)** widens the reach of four objectui validators: each now
judges nodes held in a renderer's declared slots, not only in
`children`. It adds, removes or renames no ObjectStack-authorable key,
and no Zod schema or stored `sys_metadata` shape moves. Its sdui-parser
half only takes effect when a manifest is built with `slotsFor`. This
repo's `scripts/gen-sdui-manifest-node.mjs` calls
`manifestFromConfigs(configs)` without that option, and the regenerated
`sdui.manifest.json` carries 0 `slots` keys. So the save gate's walk
does not move with this bump. The lockstep reading is in Acceptance
notes.

## What changed here (22 files, +255 / -93)

- **`.objectui-sha` and `.changeset/console-0abd4f9f8769.md`.**
`scripts/bump-objectui.sh 0abd4f9f8769fc4c19ad2f96707684876f74c09f
--no-commit` wrote both, with `OBJECTUI_ROOT` set to a read-only
objectui clone in the scratchpad. The range walked completely and the
level was auto-set to `minor`.
- The digest rendered objectui#11170's bullet as its first line, the
bare `**Clause-②: yes (narrowing)**`. That bullet is rewritten to say
what landed.
- A closing paragraph states the range has no declared-breaking entry
and names the release-nothing pair.
- **`sdui.manifest.json` and `scripts/sdui-manifest.record.json`.**
`node scripts/gen-sdui-manifest-node.mjs` regenerated them over the tree
that `pnpm objectui:build` built at the pin. It still has 107
components, and the sha256 moves from `6f921896ffac…` to
`f95d406a584e…`.
- One input moved: `object-master-detail-form`'s `details` gains `of:
"object"` and a description of the spec's closed entry (objectui#11396).
`"of": "object"` occurrences go from 12 to 13.
- The record moves its pin and `modulesRoot`. objectui's workspace
version stays 17.7.0, confirmed against the pin by `check-sdui-manifest
--require-objectui`.
- **`packages/sdui-parser/objectui-lockstep.json`.** `gen:sdui-lockstep`
re-recorded it from the clone at the pin. It records 214 grammar lines
(blob `0131f27cf86d`), 25 diagnostic codes and containment predicate
`76c18fb95d1f`, all unchanged, so no port is owed by that gate.
- **The 54 asserting pin citations in `packages/spec/src`, re-measured,
not restamped.**
- **Method.** The range changes 50 paths. Each asserting record's cited
objectui paths were resolved against the tree at `9dfaca654`. Ambiguous
bare names were disambiguated by the record's own directory. Each
`index.tsx` and `types.ts` cited is a `plugin-kanban`,
`plugin-dashboard`, `plugin-map`, `plugin-gantt`, `plugin-grid`,
`plugin-tree` or `plugin-timeline` file. None is
`plugin-form/src/index.tsx` or `sdui-parser/src/types.ts`, the two
same-named files the range touches.
- **Result.** No asserting record cites a changed path. So every cited
file is byte-identical across the hop, and every anchor held unmoved.
- **Records.** Each of the 41 hand-written records gains a dated
2026-10-05 hop sentence and keeps its earlier history.
- **Counts.** Three records carry a count, and each was re-taken by its
own method; all three read the same at `2e818d0b5`, `9dfaca654` and
`0abd4f9f8`:
- the `keyboardNavigation` hit lines: 15, against 3 for the
`schema.editable` control;
- `ObjectKanban.tsx`'s `quickAdd` / `onQuickAdd`: 2 each, against 11 for
`onCardClick`;
- the `ElementDataSourceGate` occurrences in the five `src/index.tsx`
shells: 0, 3, 3, 3 and 4.
- **Corpus counts.** The six migration entries' counts were re-taken
with `git grep -o -F`. That method first reproduced every `9dfaca654`
number: 7632 files, `objectstack` 17313, `@objectstack/spec` 7186,
`timeout` 1360, `useState` 2477, `TTL` 182, `tenant` 1318,
`RuntimeConfig` 293, `resourceLimits` 2, `window` 4193, `period` 238,
`interval` 195, `metrics` 401 and `Span` 508.
- The new readings are 7650 files, `objectstack` 17390,
`@objectstack/spec` 7209 and `useState` 2478. `window` reads 4194. Every
other control is unchanged.
- All 98 checked tokens (the export lists of
`plugin-lifecycle-advanced.zod.ts`, `tracing.zod.ts` and
`metrics.zod.ts`, plus every named key) read the same at both pins:
every zero is still zero, and `Span` / `SpanSchema` read 508 / 57.
- `packages/spec/src/migrations/registry.ts` was regenerated with
`gen:migration-registry`.
- **`.changeset/objectui-pin-citations-0abd4f9f8769.md`** is a
`@objectstack/spec` patch, because the `FormField.span` describe and six
migration descriptions name the pin.
`content/docs/references/ui/view.mdx` was regenerated by
`check:generated --fix`.

No example, test or gate needed adapting, and no code changed outside
generated records, citations and changesets.

## Console build (the local Console Pin Gate equivalent)

`turbo run build --filter=@objectstack/client...
--filter=@objectstack/spec...` and then `pnpm objectui:build` ran as one
command under the verify lock. That is a clean build: mode 3
shallow-cloned objectui at the pin into `.cache/objectui-0abd4f9f8769`.
Exit 0, 10m51s on the shared box. The build log reports:
- "Bundle canary 'import/jobs' present".
- "Single-zod canary: exactly one zod version literal
{major:4,minor:6,patch:5}".
- **"Console bundle carries THIS tree's @objectstack/spec, and only
it"**: the spec-injection check passes.
- "@objectstack/console dist ready (64232 KB) from
objectui@0abd4f9f8769".

`check:console-sha` and `check:console-injection` (self-test 67
assertions, then the dist check) exit 0 against that dist.

## Browser smoke: `examples/app-showcase` with the Console built at
`0abd4f9f8769`

The server ran `pnpm dev -- --fresh --ui --no-watch -p 41907` with
`OS_PORT=41907`, on its own ephemeral DB with the seeded admin. Headless
Chromium (`/opt/pw-browsers/chromium`) drove it, signing in through the
console's login form. Page errors, console errors and every 4xx/5xx
response were captured. Only the PIDs this run started were stopped.

**Flow label (objectui#11636 / objectui#11092).** The launch was Tasks
list, select a row, then the toolbar's "Reassign…"
(`showcase_bulk_reassign`, which targets the screen flow
`showcase_reassign_wizard`, label "Reassign Task").

| leg | trigger answer | runner header line | dialog title (accessible
name) | API name shown | completion toast |
|---|---|---|---|---|---|
| `en` | 200, `status: paused`, `flowLabel: "Reassign Task"` | `Reassign
Task` | `New Assignee` (the screen's own title) | no | `Flow "Reassign
Task" completed` |
| `zh-CN`, with a bundle entry `flows.showcase_reassign_wizard.label` |
200, `flowLabel: "Reassign Task"` | `重新分配任务` | `New Assignee` | no |
`流程「重新分配任务」已完成` |

- The resume answered 200 with `flowLabel` on both legs.
- **The showcase bundle declares no `flows` translations** (`git grep`
finds none), so the `zh-CN` leg needed one to exist. It was made by a
temporary local edit, as objectstack-ai#21800's smoke did for `imageField`:
- `node scripts/ablation-replace.mjs --hold` added `flows: {
showcase_reassign_wizard: { label: '重新分配任务' } }` to the `zh-CN` block of
`examples/app-showcase/src/system/translations/index.ts`.
  - The server booted with `--compile`.
- The file was restored at once with `--restore`. The tool reports the
blob back to HEAD's `f0517049b565` and `git diff HEAD` empty, and `git
status --porcelain` is empty.
- `GET /api/v1/i18n/translations/zh-CN` served the entry, and the
session's `html[lang]` read `zh-CN`.
- The showcase `dist/` was rebuilt afterwards (`turbo run build
--filter=@objectstack/example-showcase --force`), and the held string
has 0 hits in `dist/objectstack.json`. Nothing of the edit is in this
diff.
- **Verdict:** at this pin the runner header and the completion toast
name the flow by the active language's `flows.FLOW.label`, then the
served label, and never by the API name. The launcher button still reads
the ACTION's label ("Reassign…"), which is the action's own string and
not the flow's.

**The range's other landings.**

| landing | surface | observed |
|---|---|---|
| objectui#11640 (objectui#11628) | Setup →
`metadata/datasource/showcase_external` | The External Datasource panel
lists the remote tables `customers` (7 columns) and `orders` (7), each
with Import. "Refresh catalog" (`POST …/external/refresh-catalog` 200)
shows `snapshot 10/5/2026, 5:25:23 AM`. "Run validation" (`POST
…/external/validate` 200) renders "All 2 objects match the remote
schema." with `showcase_ext_customer` and `showcase_ext_order`, and no
render error. |
| objectui#11624 (objectui#11396) | `page/showcase_project_workspace`
(`object-master-detail-form`, `details: [{ title: 'Tasks', childObject:
'showcase_task', addLabel: 'Add task' }]`) | The master form draws its 6
fields. The Tasks section draws, and "Add task" opens the child's inline
form with 14 more fields (Title*, Assignee, Priority, …). 0 page errors.
|
| objectui#11637 (objectui#11170) | the three `kind:'html'` pages:
`showcase_start_here`, `showcase_capability_map`,
`showcase_command_center_jsx` | All three render (1563 / 2426 / 882
characters of text), with no compile or validation text and 0 page
errors. |
| objectui#11635 (objectui#11095) | `dashboard/showcase_ops_dashboard`,
`showcase_revenue_pulse`, `showcase_chart_gallery` | The ops tiles read
Active Projects 2, At-Risk (Red) 1, Awaiting Review 2 and Total Budget
1,090,000, matching objectstack-ai#21710's REST cross-check. All three dashboards have
0 page errors. This landing changes no executable code (its changeset:
tests and one doc comment). An out-of-band REST `PATCH` of a task to
`in_review` did not re-read the open tile: it stayed 2 with 0 dataset
queries in 6s, and read 3 after a reload. That mutation never travels
the console's own invalidation bus, so this is no reading of
objectui#11095's pin. **NOT MEASURED** there. |

**Console messages across the run:** 0 page errors. The failed loads are
the pre-login `401 GET /api/v1/auth/get-session`, one `404` per page
load that the response listener did not attribute (`/favicon.ico`
answers 404 on this server, as objectstack-ai#21800 recorded), and `404 GET
/api/v1/meta/datasource/showcase_external?state=draft`, the panel's
draft probe for a datasource that has no draft.

## Gates and tests (head `e40526e564`)

- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 127 commands from the 22-path diff, and all 127 were
run at `e40526e564` and exited 0. `--ran` reports "127 derived, 127 run,
0 NOT-MEASURED, 0 UNRUN", with every exit code recorded. The full
workspace build (`turbo run build --filter=!@objectstack/docs`) ran
first, so no dist-reading gate met a missing prerequisite.
- Also exit 0:
- `check:objectui-pin-citations --verify-anchors` with `OBJECTUI_ROOT`
at the pin: "54 asserting objectui pin citation(s) match .objectui-sha
(0abd4f9f8)", and "7 anchor content assertion(s) verified against
objectui at 0abd4f9f8".
- `check:objectui-bump` (20 assertions across 5 cases),
`check:sdui-lockstep`, `check-sdui-manifest --require-objectui`,
`check:console-sha`, `check:console-injection`.
- `check-adr-0087-registration --base origin/main` and
`check-changeset-no-major --base origin/main`.
- `@objectstack/spec check:generated`: all 15 artifacts current after
the `--fix`.
- `pnpm --filter @objectstack/spec exec vitest run`: 668 files, 19261
passed, 1 todo. `pnpm --filter @objectstack/spec typecheck`: exit 0.
- `@objectstack/sdui-parser` test (14 files, 225 passed) and typecheck:
exit 0.
- These suites read the regenerated manifest:
  - `@objectstack/lint` (119 files, 5627 passed);
- `@objectstack/metadata-protocol`
`src/protocol.runtime-authoring-gate.test.ts` (70 passed);
- `@objectstack/cli` unit tier `src/utils/sdui-manifest.test.ts` and
`test/validate-build-gate-parity.test.ts` (2 files, 79 passed).
  - The CLI integration tier is declared to CI.
- `eslint --no-inline-config --format json` on the 15 changed TS files:
15 files, 0 errors and 0 warnings.
- The lint population is `**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`
(`eslint.config.mjs:971`).
- The config enables no type-aware linting (`:328`), so this diff cannot
move a verdict on an untouched file.
  - Repo-wide `pnpm lint` is left to CI.

## Acceptance notes

- **Lockstep, a reading and not a gate result.** objectui#11170 gives
objectui's `validateTree` a slot walk: `slotElements` plus the
`comp?.slots` loop in `packages/sdui-parser/src/validate.ts`. This
repo's port has none (`git grep -n slots packages/sdui-parser/src`: 0).
- The walk only fires for a manifest built with `slotsFor`, and this
repo's committed manifest carries no `slots`. So the save gate and
objectui's runtime parser still agree on that manifest.
- objectui's `kind:'html'` compile (`getJsxManifest`) is now built with
`slotsFor`. A slot-held node that fails validation could fail the
renderer's compile while this repo's save gate accepts it.
- `check:sdui-lockstep` compares the grammar region, the codes and the
containment predicate, so it cannot see a walk-depth change. That class
is outside its reach by its own header ("CANNOT see … WHEN a code fires"
beyond the predicate).
- Not measured through a public door here, so it is noted rather than
filed. Carrier: none.
- `main` moved three commits past this branch's base (objectstack-ai#21810, objectstack-ai#21808,
objectstack-ai#21809). `git merge-tree --write-tree HEAD origin/main` is clean. Two of
them touch files this diff touches:
`api-methods-batch-conformance.test.ts` (test titles) and
`component.test.ts` (objectstack-ai#21808 re-points a non-asserting anchor). The
merged tree still carries 1 and 6 citations at the new pin and 0 at the
old one. No merge was made; the merge queue rebuilds on the current
`main`.
- objectui `main` reached `59917c4b2` (objectui#11639) after the pin was
read. It is not in this range.
- Writes: one draft PR through the relay and the report comment on
objectstack-ai#21807. No label, PR assignee, ready flag or auto-merge was written;
this dispatch's write budget names none of them.

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

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…jectstack-ai#21943)

Part of objectstack-ai#21932

Clause-②: no

## What changes

The platform checklist gains items for the rules the 17.7 pre-release
security follow-up landed, and two re-checks from the card are resolved.
All edits are in `docs/qa/platform-checklist/areas/*.json`.
`automation.json` is untouched (open PR objectstack-ai#21928 holds it).

| Card row | Disposition | Item |
|---|---|---|
| objectstack-ai#21792 (PR objectstack-ai#21809) settings audit and secret-valued settings | new
item | `platform-core.settings-audit-secret-fingerprint` |
| objectstack-ai#21846 (PR objectstack-ai#21872) implicit account linking | new item |
`identity-auth.implicit-account-linking-ownership` |
| objectstack-ai#21839 (PR objectstack-ai#21890) share-link password | three clauses added, rev 4 to
5 | `access-security.share-link-capability-tokens` |
| objectstack-ai#21836 (PR objectstack-ai#21879) global search skips unreadable objects, plus the
two cases objectstack-ai#21880 lists | new item |
`search.global-search-skips-unreadable` |
| re-check 1: A2 / A7 and the plugin-driver boundary | rev 2 to 3 |
`integration-system.datasource-credential-refusal-matrix` |
| re-check 2: the objectstack-ai#21845 CLI and quorum N1 notes | already applied by
objectstack-ai#21891, no edit | `cli.scaffold-first-run`,
`cli.scaffold-console-first-paint`, `approvals.quorum-m-of-n` |

Each item states rules, not reproductions. Withheld security detail
stays out.

### Grounding, per row

- **Settings audit fingerprint.** Both ledgers record the keyed digest
for a secret-valued setting, or no fingerprint when none is available,
and never the value or an unkeyed hash. Grounded in
`settings-service.ts#secretAuditDigest`,
`config-change-audit.ts#CONFIG_CHANGE_ACTION` and the contract text at
`crypto-provider.ts#keyedDigest`. The pin is
`settings-audit-secret-digest.test.ts` (7 cases). The offline check
carries a positive control: the non-secret key's unkeyed digest IS
found, so a no-hit on the secret rows means something. The
no-keyed-digest arm cannot be reached on a stock boot, so that clause is
scored from the pin.
- **Implicit account linking.** Four rules: no implicit link to an
unverified local user; an unlink is honoured; an explicit, signed-in
link still works and lifts the refusal; the platform IdP exception holds
only on its OAuth path. Grounded in `implicit-account-linking.ts`
(`decideImplicitLink`, `IMPLICIT_LINK_REFUSED`,
`PLATFORM_IDP_PROVIDER_ID`, `recordUnlinkTombstone`,
`refuseImplicitAccountLink`) and the published `sso.mdx` section. The
pin is `implicit-account-linking.test.ts`. The item reuses the local
OIDC provider recipe from `identity-auth.linked-accounts-social`. The
platform-IdP clause and the operator override are pin-scored, and
knownGaps says why.
- **Share-link password.** The stored hash leaves on no exit (mint,
list, redemption). The password is accepted from the `X-Share-Password`
header, the query form is still accepted, and the default CORS
allow-list carries the header. Both public routes answer `Cache-Control:
no-store` and `Vary: X-Share-Password` on every outcome, and the
authenticated routes do not. Grounded in
`share-link-service.ts#withoutPasswordHash`,
`share-link-routes.ts#SHARE_LINK_PUBLIC_RESPONSE_HEADERS`, the runtime
`share-links.ts#PUBLIC_RESPONSE_HEADERS` and
`adapter.ts#DEFAULT_CORS_ALLOW_HEADERS`. The pins are the `[objectstack-ai#21839]`
blocks in `share-link-password.test.ts`,
`share-links-public-cache-headers.test.ts` and the hono-plugin CORS
case. Existing clause indices are unchanged.
- **Global search.** An unreadable object is never queried, named or
counted. An explicit `objects=` naming one answers exactly as a name
that matches no object. The object stays refused at its own door. Row
scope still narrows a searched object, and a term found only in a field
hidden from the caller yields no hit. Grounded in
`protocol.ts#searchAll` (the `canReadObject` pre-filter and the
`getQueryableFields` narrowing). The pins are the dogfood
`search-skip-unreadable.dogfood.test.ts` and the 12 unit cases in
`protocol.search-skip-unreadable.test.ts`. The two objectstack-ai#21880 cases have no
end-to-end pin yet, and knownGaps says so. The open pinyin-companion
finding on objectstack-ai#21880 is recorded as a knownGap with a flag-off instruction,
at class level only. The persona reuses the area recipe
`qa-contributor-bound-member`.
- **Datasource credential matrix.** A2 / A7 (`acceptance[1]` and
`acceptance[6]`) are recorded as a known environment gap. They need a
reachable credential-protected database of a shipped driver, which no
run has had. No recipe is claimed, because none is proven. A successful
publish alone may not score them, and the stored-credential half of A7
can be read as a partial reading. Separately, the unknown-driver clause,
step 7, its negative and the title now state the ruled boundary from
objectstack-ai#21921 and the docs note objectstack-ai#21927. For a plugin driver, only the fixed
spellings are redacted (the canonical keys, the former aliases and URL
credentials). A non-canonical key served as written is the boundary, not
a FAIL. Grounded in `common.zod.ts#CANONICAL_CREDENTIAL_KEYS` and
`datasource-credential-redaction.ts#redactableConfigKeys`.

### Re-check 2 evidence (no edit)

At the claim ref `9dce635337`:

- `cli.scaffold-first-run` (rev 3) step 0 and
`cli.scaffold-console-first-paint` (rev 3) step 0 both drop the trailing
`npm install` and warn against adding it. Their rev 3 history entries
cite objectstack-ai#21845. No other `npm install` step remains in `cli.json`.
- `approvals.quorum-m-of-n` (rev 4) `negative[0]` requires a
NON-PRIVILEGED repeat actor and names the documented admin override
(objectstack-ai#3424) as never a distinctness FAIL.

## Remaining on objectstack-ai#21932 (held, not in this PR)

- The objectstack-ai#21864 row (public-form withdrawal layering). Its PR is still
open.
- The objectstack-ai#21928 row (run-state trigger record mask). That PR adds its own
item in `automation.json`.

objectstack-ai#21932 remains open for these two rows.

## Validation (at `a72b827e43`)

- `pnpm check:platform-checklist`: exit 0. It reports 15 areas and 273
items (269 active, 2 planned). The baseline was 270. Symbol anchors
resolve 674 of 684 (baseline 657 of 667): all 17 new anchors resolve,
and the objectstack-ai#16898 residual is unchanged at 10.
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 13 commands, and all 13 exit 0.
`check:doc-formula-expressions` first exited 3 (PREREQUISITE NOT MET:
`@objectstack/formula` and `@objectstack/lint` were not built). After
building them it exited 0. `--ran` reconciliation: 13 derived, 13 run, 0
unrun.
- No package source changed, so there is no package build, test or
typecheck. No changeset: `docs/qa/**` publishes nothing.

## Acceptance notes

- Source citations name test cases and symbols, never line numbers,
because `check:platform-checklist` refuses a `file:line` pin.
- `content/docs/data-modeling/drivers.mdx` says a plugin driver's
`config` is "stored and served to administrators as written". The read
redactor still withholds the canonical spellings (`password`,
`authToken`), the former aliases and URL credentials for such a driver
(`redactableConfigKeys`). So the docs sentence is slightly broader than
the code, and the code is the more protective of the two. The checklist
follows the code. This is noted only, with no card. Carrier: none.
- A run of `search.global-search-skips-unreadable` picks the walled
object and the hidden-field value on the live boot, behind premise
guards. The item names likely candidates and does not assume them.

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

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 size/m tests tooling

Projects

None yet

1 participant