diff --git a/docs/qa/platform-checklist/README.md b/docs/qa/platform-checklist/README.md index f0d083d76d..0ab629db6b 100644 --- a/docs/qa/platform-checklist/README.md +++ b/docs/qa/platform-checklist/README.md @@ -165,6 +165,35 @@ Why this shape: tell a rotted one from a fresh one — so it keeps reading as "verified against source" while pointing somewhere else. The file plus the symbol name is already the load-bearing half. `check:platform-checklist` fails on any that reappear. +- **Write the symbol as an ANCHOR where the gate can resolve one** — the file path, a + `#`, and the symbol, with the prose beside it unchanged: + + ```text + packages/core/src/security/platform-admin.ts#parsePlatformAdminEmails (whole-variable refusal) + ``` + + `check:platform-checklist` resolves every anchor: the file must be in this repo and the + symbol must still be in it, **comments masked** — a symbol surviving only in a docblock + is ABSENT, because prose about a symbol is not a symbol. A symbol inside a string + literal counts as present: much of what this ledger cites *is* a string (capability + names, error codes, `sys_*` machine names). Dotted anchors resolve segment by segment. + This is presence-in-code, not a declaration analysis — what it catches is the rename or + deletion that leaves the pointer naming nothing, which is exactly the rot the line + numbers used to carry. +- **A citation the gate cannot resolve stays BARE — that is honest, not a gap.** Anchors + are only for extensions the gate reads symbols out of (`.ts .tsx .mts .cts .js .mjs + .cjs .json`); an anchor on a `.md` or a `.yml` is an ERROR rather than a silent skip, + since a spelling that resolves nowhere is the false pointer all over again. Same for a + sibling-repo citation (`objectui …`) and for a file with no symbol worth naming: cite + the path, put the detail in prose. +- **Anchors are shrink-never.** Deleting a `#symbol` is the silent way past any red the + resolver prints, so the count per family file is floored in + [`scripts/checklist-symbol-anchor-baseline.json`](../../../scripts/checklist-symbol-anchor-baseline.json) + (authored from `node scripts/check-platform-checklist.mjs --anchor-census`, which is a + census and not a verdict). Adding anchors never reds; ⛔ lowering a floor is + MAINTAINER-ONLY. ⚠️ Note what a red here reaches: `check:platform-checklist` is not + CI-wired (see "Operating cadence"), so it reaches whoever runs the gate — **not every + PR**. #11730 holds that gap. - **`why` is the debt marker.** A recipe exists because stock fixtures cannot demonstrate something — the same discipline as a coverage waiver. Landing the fixture in the showcase seeds proper retires the recipe; until then `why` says what is missing and diff --git a/docs/qa/platform-checklist/areas/access-security.json b/docs/qa/platform-checklist/areas/access-security.json index ecb32a9623..0136c48bc9 100644 --- a/docs/qa/platform-checklist/areas/access-security.json +++ b/docs/qa/platform-checklist/areas/access-security.json @@ -200,8 +200,8 @@ "release-15.1 plan §A1–A4", "#3106", "#5126", - "examples/app-showcase/src/data/objects/contact.object.ts (lead_score)", - "examples/app-showcase/src/data/objects/invoice.object.ts (readonlyWhen)" + "examples/app-showcase/src/data/objects/contact.object.ts#lead_score (lead_score)", + "examples/app-showcase/src/data/objects/invoice.object.ts#readonlyWhen (readonlyWhen)" ], "history": [ { @@ -370,7 +370,7 @@ "#3358 §5", "packages/spec/src/api/error-code-ledger.zod.ts (@objectstack/metadata-protocol codes)", "ADR-0010 §3.3 (_lock)", - "packages/runtime/src/domains/packages.ts (POST /packages/:id/duplicate — ADR-0070 D4 'duplicate base', the writable-target route)", + "packages/runtime/src/domains/packages.ts#packages (POST /packages/:id/duplicate — ADR-0070 D4 'duplicate base', the writable-target route)", "docs/adr/0070-package-first-authoring.md §D4 ('clone a base into a new writable package')" ], "history": [ @@ -610,7 +610,7 @@ ], "source": [ "packages/spec/src/security/sharing.zod.ts (the four-model enum — variant source)", - "examples/app-showcase/access-matrix.json (sharingModel column)", + "examples/app-showcase/access-matrix.json#sharingModel (sharingModel column)", "ADR-0055", "authz-conformance.matrix.ts rows owd-private / owd-public-read / controlled-by-parent", "cross-ref access-security.rls-both-sides clause 5 — canonical home of the private D11 expected-fail probe (#7637, #7463 ruling)" @@ -819,7 +819,7 @@ ], "source": [ "examples/app-showcase/src/security/permission-sets.ts (ADR-0057 D1 dials)", - "examples/app-showcase/access-matrix.json (readScope/writeScope columns — variant source)", + "examples/app-showcase/access-matrix.json#readScope (readScope/writeScope columns — variant source)", "ADR-0057 D1" ], "history": [ @@ -1110,11 +1110,11 @@ "wrong-persona" ], "source": [ - "packages/rest/src/rest-route-ledger.ts (record-shares family: GET/POST /data/:object/:id/shares, DELETE .../:shareId; sharing-rules family evaluate)", - "packages/rest/src/rest-server.ts (registerRecordShareEndpoints ~L7246-7331; registerSharingRuleEndpoints evaluate ~L7477-7493)", - "packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts (recipient_type/recipient_id/access_level/source fields)", - "packages/plugins/plugin-sharing/src/sharing-rule-service.ts (evaluateRule → SharingRuleEvaluationResult)", - "examples/app-showcase/src/security/sharing-rules.ts (share_key_account_qualified_contacts_with_managers seeded criteria rule)", + "packages/rest/src/rest-route-ledger.ts#shareId (record-shares family: GET/POST /data/:object/:id/shares, DELETE .../:shareId; sharing-rules family evaluate)", + "packages/rest/src/rest-server.ts#registerSharingRuleEndpoints (the evaluate leg; the record-shares family GET/POST /data/:object/:id/shares + DELETE .../:shareId is registered inline, under no registrar of its own — #13788 measured that the registerRecordShareEndpoints this citation used to name is not in the file)", + "packages/plugins/plugin-sharing/src/objects/sys-record-share.object.ts#recipient_type (recipient_type/recipient_id/access_level/source fields)", + "packages/plugins/plugin-sharing/src/sharing-rule-service.ts#evaluateRule (evaluateRule → SharingRuleEvaluationResult)", + "examples/app-showcase/src/security/sharing-rules.ts#share_key_account_qualified_contacts_with_managers (share_key_account_qualified_contacts_with_managers seeded criteria rule)", "ADR-0111 D1/D4/D5" ], "history": [ @@ -1221,8 +1221,8 @@ "stale-console-bundle" ], "source": [ - "packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts (action enum, actor/user_id/object_name/record_id/old_value/new_value fields, enable.apiMethods get+list)", - "packages/plugins/plugin-audit/src/audit-plugin.ts (nav_audit_logs → sys_audit_log)", + "packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts#user_id (action enum, actor/user_id/object_name/record_id/old_value/new_value fields, enable.apiMethods get+list)", + "packages/plugins/plugin-audit/src/audit-plugin.ts#nav_audit_logs (nav_audit_logs → sys_audit_log)", "objectui apps/console/src/pages/system/AuditLogPage.tsx (/api/v1/data/sys_audit_log, $filter/$orderby/$top/$skip, Before/After drawer)", "objectui apps/console/src/AppContent.tsx (route system/audit-log)" ], @@ -1320,10 +1320,10 @@ "hydration-race" ], "source": [ - "packages/plugins/plugin-security/src/object-posture-gate.ts (R1 owd_widening_forbidden / R2 owd_external_wider, 403)", - "packages/lint/src/validate-security-posture.ts (OWD_WIDTH + SECURITY_EXTERNAL_WIDER lint parity)", + "packages/plugins/plugin-security/src/object-posture-gate.ts#owd_widening_forbidden (R1 owd_widening_forbidden / R2 owd_external_wider, 403)", + "packages/lint/src/validate-security-posture.ts#OWD_WIDTH (OWD_WIDTH + SECURITY_EXTERNAL_WIDER lint parity)", "objectui packages/app-shell/src/views/studio-design/owd-sharing.ts (isExternalWider) + PackageOwdOverviewPanel.tsx + ObjectSettingsPanel.tsx", - "examples/app-showcase/src/data/objects/announcement.object.ts + account.object.ts (externalSharingModel declarations)", + "examples/app-showcase/src/data/objects/announcement.object.ts#externalSharingModel + account.object.ts (externalSharingModel declarations)", "ADR-0090 D11, ADR-0086 D1, ADR-0124 D1, #3050, objectui#2508" ], "history": [ @@ -1421,11 +1421,11 @@ "stale-console-bundle" ], "source": [ - "examples/app-showcase/src/ui/views/inquiry.view.ts (formViews.contact: allowAnonymous, publicLink '/forms/contact-us', whitelist, submitBehavior)", - "examples/app-showcase/src/data/objects/inquiry.object.ts (sharingModel private, publicFormGrant docblock, server-controlled status/source)", + "examples/app-showcase/src/ui/views/inquiry.view.ts#formViews (formViews.contact: allowAnonymous, publicLink '/forms/contact-us', whitelist, submitBehavior)", + "examples/app-showcase/src/data/objects/inquiry.object.ts#sharingModel (sharingModel private, publicFormGrant docblock, server-controlled status/source)", "examples/app-showcase/src/data/hooks/index.ts (guest-defaults stamping)", "packages/rest/src/rest-route-ledger.ts (forms family: GET /forms/:slug, POST /forms/:slug/submit, GET /forms/:slug/lookup/:field)", - "packages/rest/src/public-form-routes.test.ts (#3022 anchor-enforcement: whitelist strip, __proto__ guard, publicFormGrant, LOOKUP_NOT_PUBLIC)", + "packages/rest/src/public-form-routes.test.ts#__proto__ (#3022 anchor-enforcement: whitelist strip, __proto__ guard, publicFormGrant, LOOKUP_NOT_PUBLIC)", "ADR-0056 Option A, #3022/#3036/#3004" ], "history": [ @@ -1528,8 +1528,8 @@ "wrong-persona" ], "source": [ - "packages/runtime/src/domains/share-links.ts (resolve/create/list/revoke; 401 NEEDS_PASSWORD/WRONG_PASSWORD/SIGN_IN_REQUIRED, 410 EXPIRED_OR_REVOKED/RECORD_GONE, applyRedaction)", - "packages/plugins/plugin-sharing/src/share-link-service.ts (getPolicy publicSharing gate → 422 SHARING_NOT_ENABLED, resolveToken audience/password/expiry, #5190 recordStillExists fail-closed, list createdBy scoping)", + "packages/runtime/src/domains/share-links.ts#WRONG_PASSWORD (resolve/create/list/revoke; 401 NEEDS_PASSWORD/WRONG_PASSWORD/SIGN_IN_REQUIRED, 410 EXPIRED_OR_REVOKED/RECORD_GONE, applyRedaction)", + "packages/plugins/plugin-sharing/src/share-link-service.ts#getPolicy (getPolicy publicSharing gate → 422 SHARING_NOT_ENABLED, resolveToken audience/password/expiry, #5190 recordStillExists fail-closed, list createdBy scoping)", "packages/runtime/src/route-ledger.ts (share-links rows incl. public resolve/messages)", "packages/plugins/plugin-sharing/src/objects/sys-share-link.object.ts", "ADR-0047, ADR-0111 D8, #5190", @@ -1645,7 +1645,7 @@ ], "source": [ "packages/runtime/src/domains/security.ts (GET list / POST :id/confirm|dismiss; 400 unknown-status, 401 anon, 403/404/409 typed-error mapping)", - "packages/plugins/plugin-security/src/suggested-audience-bindings.ts (syncAudienceBindingSuggestions convergent reconcile; SuggestionNotFoundError 404, SuggestionStateError 409; confirm under caller context)", + "packages/plugins/plugin-security/src/suggested-audience-bindings.ts#syncAudienceBindingSuggestions (syncAudienceBindingSuggestions convergent reconcile; SuggestionNotFoundError 404, SuggestionStateError 409; confirm under caller context)", "packages/plugins/plugin-security/src/objects/sys-audience-binding-suggestion.object.ts", "packages/rest/src/rest-route-ledger.ts (security suggested-bindings rows)", "examples/app-showcase/src/security/bind-position-sets.ts + permission-sets.ts (isDefault auto-bind)", @@ -1752,11 +1752,11 @@ "hydration-race" ], "source": [ - "packages/rest/src/rest-server.ts (registerSharingRuleEndpoints ~L7345-7494: list/create/get/delete)", + "packages/rest/src/rest-server.ts#registerSharingRuleEndpoints (list/create/get/delete)", "packages/spec/src/security/sharing.zod.ts (criteria rule authoring + match-all refusal #3896)", "packages/plugins/plugin-sharing/src/objects/sys-sharing-rule.object.ts + sys-record-share.object.ts", - "objectui packages/plugins/plugin-sharing/src/sharing-plugin.ts (nav_sharing_rules Setup nav → sys_sharing_rule)", - "examples/app-showcase/src/data/objects/contact.object.ts (showcase_contact — sharingModel 'private', `stage` select) + src/security/permission-sets.ts (the showcase_manager readScope 'org' grant this item must avoid as its audience)", + "objectui packages/plugins/plugin-sharing/src/sharing-plugin.ts#nav_sharing_rules (nav_sharing_rules Setup nav → sys_sharing_rule)", + "examples/app-showcase/src/data/objects/contact.object.ts#sharingModel (showcase_contact — sharingModel 'private', `stage` select) + src/security/permission-sets.ts (the showcase_manager readScope 'org' grant this item must avoid as its audience)", "cross-ref access-security.sharing-rules-widen (seeded-rule enforcement), ADR-0058 D3, ADR-0111 D6" ], "history": [ @@ -1859,8 +1859,8 @@ ], "source": [ "objectui packages/app-shell/src/views/metadata-admin/PermissionMatrixEditor.tsx (type=permission editor; client.save → PUT /api/v1/meta/permission/; object CRUD+VAMA+lifecycle + field R/W)", - "packages/spec/src/security/permission.zod.ts (PermissionSetSchema)", - "packages/rest/src/rest-route-ledger.ts (PUT /api/v1/meta/:type/:name saveItem; POST .../publish publishItem)", + "packages/spec/src/security/permission.zod.ts#PermissionSetSchema (PermissionSetSchema)", + "packages/rest/src/rest-route-ledger.ts#saveItem (PUT /api/v1/meta/:type/:name saveItem; POST .../publish publishItem)", "examples/app-showcase/src/security/permission-sets.ts", "cross-ref access-security.crud-permission-matrix + access-security.readonly-package-locks-studio, ADR-0090 D1/D5, ADR-0033" ], @@ -1955,13 +1955,13 @@ ], "source": [ "packages/spec/liveness/capability.json (ADR-0066 D1, #5961 — the kind's own liveness ledger)", - "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts (upsertPackageCapability, capabilityRowFields, PLATFORM_CAPABILITY_NAMES guard)", - "packages/lint/src/validate-capability-references.ts (authoring lint known-name set)", + "packages/plugins/plugin-security/src/bootstrap-declared-capabilities.ts#upsertPackageCapability (upsertPackageCapability, capabilityRowFields, PLATFORM_CAPABILITY_NAMES guard)", + "packages/lint/src/validate-capability-references.ts#known (authoring lint known-name set)", "examples/app-showcase/src/security/{capabilities.ts,permission-sets.ts} (DEFINE + GRANT fixtures)", - "packages/runtime/src/action-execution.ts (actionPermissionError — ADR-0066 D4, the single-sourced REQUIRE-side gate every invoke surface calls)", + "packages/runtime/src/action-execution.ts#actionPermissionError (actionPermissionError — ADR-0066 D4, the single-sourced REQUIRE-side gate every invoke surface calls)", "packages/runtime/src/domains/{actions.ts,mcp.ts} + packages/runtime/src/route-ledger.ts (the platform action-invoke surfaces that reach it)", "examples/app-showcase/src/ui/actions/predicate-matrix.action.ts (the four REQUIRE-side specimens: held / missing / and / empty)", - "packages/rest/src/rest-route-ledger.ts `data-actions` block (why there is no REST action-invoke route, and the day-one enforcement requirement if one is ever added — #7680)" + "packages/rest/src/rest-route-ledger.ts#data `data-actions` block (why there is no REST action-invoke route, and the day-one enforcement requirement if one is ever added — #7680)" ], "history": [ { @@ -2191,10 +2191,10 @@ "ref": "packages/runtime/src/domains/automation-activation-posture-gate.test.ts (#12157, flow door) + packages/runtime/src/domains/action-activation-posture-gate.test.ts (#12160, action door) — the full posture x identity matrix for both doors, including the group/isolated legs this item marks blocked(fixture), the no-write-on-refusal assertion, and the isSystem bypass" }, "source": [ - "packages/runtime/src/domains/activation-gate.ts (the shared §5 gate: posture read 141-147, platform_admin POSITION test 149-150, manage_metadata tier 184-203, per-artifact remedies 100-118, fail-open rationale 50-56)", - "packages/runtime/src/domains/automation.ts (FLOW_ENABLEMENT_DENY_MESSAGE), (isFlowActivationWrite), (gate ordering ahead of service probe and body checks)", + "packages/runtime/src/domains/activation-gate.ts#posture (the shared §5 gate: posture read 141-147, platform_admin POSITION test 149-150, manage_metadata tier 184-203, per-artifact remedies 100-118, fail-open rationale 50-56)", + "packages/runtime/src/domains/automation.ts#FLOW_ENABLEMENT_DENY_MESSAGE (FLOW_ENABLEMENT_DENY_MESSAGE), (isFlowActivationWrite), (gate ordering ahead of service probe and body checks)", "packages/runtime/src/domains/actions.ts (both tiers at the action door)", - "packages/spec/src/security/tenancy-posture.ts (TenancyPostureSchema — the variants source; postureEnforcesWall; the ADR-0105 D12 entitlement note)", + "packages/spec/src/security/tenancy-posture.ts#TenancyPostureSchema (TenancyPostureSchema — the variants source; postureEnforcesWall; the ADR-0105 D12 entitlement note)", "docs/adr/0126-packaged-metadata-customization-model.md §5 (D3: install-level rows, operator-gated in multi-org postures)", "#12438 (the sweep), #12159 (ADR-0126 flow legs), #10243 (the measured incident the gate makes durable), #12157 / #12160 (the two door cards)" ], @@ -2272,8 +2272,8 @@ "dispatcher-vs-hono-route" ], "source": [ - "packages/runtime/src/domains/automation.ts (PUT/DELETE /automation/:name → registerFlow/unregisterFlow, manage_metadata gate only)", - "packages/services/service-automation/src/engine.ts (registerFlow/unregisterFlow — zero lock/provenance check on this path)", + "packages/runtime/src/domains/automation.ts#registerFlow (PUT/DELETE /automation/:name → registerFlow/unregisterFlow, manage_metadata gate only)", + "packages/services/service-automation/src/engine.ts#registerFlow (registerFlow/unregisterFlow — zero lock/provenance check on this path)", "docs/adr/0126-packaged-metadata-customization-model.md §2 (Regime C: 'the packaged base is locked — in-place edit refused loudly at the write door') + §6 wall 2 (sole definition ledger)", "packages/plugins/plugin-security/src/packaged-permission-set-lock.ts (the same lock implemented at the permission-set data door — the regime's first instance, proving the refusal shape the /automation door lacks)", "cross-ref access-security.readonly-package-locks-studio (the /meta door's locked-base class on objects) + access-security.owd-save-gate rev 3 (why the /meta control leg records the answering layer instead of assuming it)", @@ -2378,7 +2378,7 @@ "source": [ "packages/plugins/plugin-security/src/packaged-permission-set-lock.ts (classifier + both refusals; the SchemaRegistry-not-managed_by decision)", "packages/plugins/plugin-security/src/permission-set-projection.ts (insert-path lock + duplicate 409; the #4669 row-state carve-out ordered BEFORE the update-path lock)", - "packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts (activate/deactivate bodyExtra {active}, clone_permission_set params — the payload contract)", + "packages/plugins/plugin-security/src/objects/sys-permission-set.object.ts#bodyExtra (activate/deactivate bodyExtra {active}, clone_permission_set params — the payload contract)", "docs/adr/0126-packaged-metadata-customization-model.md §1.1 (the #11513 machinery, named as the regime's first instance), §8 item 3 (permission sets converge later; the landed shape stays valid meanwhile)", "#11513 (lock the base, clone to customize — the 2026-08-24 ruling), #11703 (the three dropped clone facets), #12159 (ruling 2026-08-26: no ledger rows for permission sets pending the convergence card), #12438 (the sweep)" ], @@ -2482,12 +2482,12 @@ "ref": "packages/core/src/security/resolve-authz-context.platform-admin-config.test.ts (the acceptance criterion, all four fail-closed arms, the ⭐ stored-row-not-seed pin, the legacy-grant pointer loudness) + packages/core/src/security/platform-admin.test.ts (parse/refusal/memo) + packages/core/src/security/admin-standing-surface.test.ts. STILL MANUAL: the live-HTTP arms over a real boot (clauses 1-4 and 6) — the pins run the derivation in-process; this item's live legs prove the same verdicts through the mounted server" }, "source": [ - "packages/core/src/security/resolve-authz-context.ts (§6b-config — the config anchor inside the ONE derivation site; additive, never subtractive) + (hasPlatformAdminStanding, the id-shaped projection)", - "packages/core/src/security/platform-admin.ts (parsePlatformAdminEmails whole-variable refusal, loud-once resolve, matchesConfiguredPlatformAdmin verified-only, reportLegacyPlatformAdminGrant)", + "packages/core/src/security/resolve-authz-context.ts#hasPlatformAdminStanding (§6b-config — the config anchor inside the ONE derivation site; additive, never subtractive) + (hasPlatformAdminStanding, the id-shaped projection)", + "packages/core/src/security/platform-admin.ts#parsePlatformAdminEmails (parsePlatformAdminEmails whole-variable refusal, loud-once resolve, matchesConfiguredPlatformAdmin verified-only, reportLegacyPlatformAdminGrant)", "packages/core/src/security/admin-standing-surface.ts (the env declared a non-table derivation input — no break-glass write can reach it)", - "packages/types/src/env.ts,200 (PLATFORM_OWNER_EMAIL_ENV / resolvePlatformOwnerEmail)", + "packages/types/src/env.ts#PLATFORM_OWNER_EMAIL_ENV,200 (PLATFORM_OWNER_EMAIL_ENV / resolvePlatformOwnerEmail)", "packages/plugins/plugin-auth/src/auth-plugin.ts (#11343 — the dev seed stamps its admin's address verified)", - "packages/services/service-datasource/src/admin-routes.ts,261 (the manage_platform_settings-floor probe)", + "packages/services/service-datasource/src/admin-routes.ts#manage_platform_settings,261 (the manage_platform_settings-floor probe)", "#11663 L2 (the design and ruled bundle), #13146 (landed 2026-08-29), ADR-0068 D2 (the stored-grant anchor beside it)" ], "history": [ @@ -2592,8 +2592,8 @@ "ref": "packages/qa/dogfood/test/me-apps-and-everyone-baseline.dogfood.test.ts (the /me/apps half: member sees showcase, requiredPermissions gates, anonymous [], tabPermissions hidden drop and more-visible grant wins) + plugin-hono-server unit suites hono-current-user-endpoints.test.ts / current-user-endpoints-additive-baseline.test.ts / current-user-endpoints-position-grants.test.ts / current-user-endpoints-delegated-resolution.test.ts. STILL MANUAL: the live parity cross-check of the returned maps against actual enforcement responses (clauses 1-2) and the self-scoping probe" }, "source": [ - "packages/plugins/plugin-hono-server/src/current-user-endpoints.ts (/auth/me/permissions aggregation + most-permissive merge), (/auth/me/localization), (/me/apps requiredPermissions/tabPermissions filter), (the /api/v1 prefix)", - "packages/core/src/security/auth-gate.ts (ALLOW_SUFFIXES — /me/apps + /me/localization reachable to gated users)", + "packages/plugins/plugin-hono-server/src/current-user-endpoints.ts#tabPermissions (/auth/me/permissions aggregation + most-permissive merge), (/auth/me/localization), (/me/apps requiredPermissions/tabPermissions filter), (the /api/v1 prefix)", + "packages/core/src/security/auth-gate.ts#ALLOW_SUFFIXES (ALLOW_SUFFIXES — /me/apps + /me/localization reachable to gated users)", "#7616 (delegated permission-set resolution — the enforcement path's own answer), #2752 (/me/apps registry sourcing), #3391 (effective apiOperations annotation), #4093 (guarded degraded branch), ADR-0090 D5 (additive baseline)", "cross-ref access-security.anonymous-deny-surfaces — the 401 floor this trio is the declared exception to", "cross-ref access-security.fls-mask-and-strip — owns the FLS enforcement this item's clause 2 cross-checks" @@ -2698,8 +2698,8 @@ "ref": "packages/plugins/plugin-audit/src/read-audit.test.ts (discriminator, batching/flush, isSystem + no-principal skips, exclusion filter) + audit-plugin.test.ts. STILL MANUAL: the stock-boot honest-default (clause 1) and the configured live loop through the mounted server (clauses 2-5, fixture-gapped per knownGaps)" }, "source": [ - "packages/plugins/plugin-audit/src/read-audit.ts (READ_AUDIT_ACTION, installReadAuditWriter opt-in + exclusion filter, extractDetailReadId, buildRow nulls + view instant, isSystem/no-principal skips, batcher)", - "packages/plugins/plugin-audit/src/audit-plugin.ts (wiring readAudit options into the writer; the install log line)", + "packages/plugins/plugin-audit/src/read-audit.ts#READ_AUDIT_ACTION (READ_AUDIT_ACTION, installReadAuditWriter opt-in + exclusion filter, extractDetailReadId, buildRow nulls + view instant, isSystem/no-principal skips, batcher)", + "packages/plugins/plugin-audit/src/audit-plugin.ts#writer (wiring readAudit options into the writer; the install log line)", "content/docs/permissions/record-view-auditing.mdx ('Under os serve' — the option-less auto-instance audits no views; last-one-wins supersession is the opt-in path; no metadata key / stack key / env flag by design)", "#8992 (the card and the 2026-08-16 maintainer ruling: Option A scoped MVP — detail views only, per-object closed opt-in, async batched writes)" ], diff --git a/docs/qa/platform-checklist/areas/ai.json b/docs/qa/platform-checklist/areas/ai.json index 07739a271b..c31bd6ba72 100644 --- a/docs/qa/platform-checklist/areas/ai.json +++ b/docs/qa/platform-checklist/areas/ai.json @@ -89,11 +89,11 @@ ], "traps": ["stale-dist"], "source": [ - "packages/spec/src/ai/agent.zod.ts (requireds, aliases, retiredKey tools/knowledge, visibility/tenantId guidance)", - "packages/spec/src/ai/tool.zod.ts (.strict() + TOOL_RETIRED_KEY_GUIDANCE; READ-ONLY PROJECTION note)", - "packages/spec/src/ai/skill.zod.ts (requireds, retiredKey triggerPhrases, permissions/trigger guidance #5013)", - "packages/spec/src/kernel/metadata-type-schemas.ts (agent/tool/skill registered with schemas)", - "packages/spec/src/kernel/metadata-plugin.zod.ts (registry rows: agent allowRuntimeCreate:false ADR-0063 §2; tool/skill true; file patterns)", + "packages/spec/src/ai/agent.zod.ts#tenantId (requireds, aliases, retiredKey tools/knowledge, visibility/tenantId guidance)", + "packages/spec/src/ai/tool.zod.ts#TOOL_RETIRED_KEY_GUIDANCE (.strict() + TOOL_RETIRED_KEY_GUIDANCE; READ-ONLY PROJECTION note)", + "packages/spec/src/ai/skill.zod.ts#triggerPhrases (requireds, retiredKey triggerPhrases, permissions/trigger guidance #5013)", + "packages/spec/src/kernel/metadata-type-schemas.ts#registered (agent/tool/skill registered with schemas)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#allowRuntimeCreate (registry rows: agent allowRuntimeCreate:false ADR-0063 §2; tool/skill true; file patterns)", "packages/runtime/src/route-ledger.ts (GET /meta, GET /meta/types, GET/PUT /meta/:type/:name)", "packages/spec/liveness/agent.json + tool.json + skill.json (which props are live, and that live evidence sits in cloud)" ], @@ -178,9 +178,9 @@ "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/showcase-mcp-http-identity.dogfood.test.ts" }, "source": [ "packages/runtime/src/domains/mcp.ts (404/501/401/403/405 branches, exact messages)", - "packages/spec/src/ai/mcp.zod.ts (MCP_OAUTH_SCOPES + scopesToAgentPermissionSets, ADR-0090 D10)", - "packages/mcp/src/mcp-http-tools.ts (registered tool set; scope → tool-family narrowing)", - "packages/mcp/src/plugin.ts (OS_MCP_SERVER_ENABLED default-on semantics)", + "packages/spec/src/ai/mcp.zod.ts#MCP_OAUTH_SCOPES (MCP_OAUTH_SCOPES + scopesToAgentPermissionSets, ADR-0090 D10)", + "packages/mcp/src/mcp-http-tools.ts#registered (registered tool set; scope → tool-family narrowing)", + "packages/mcp/src/plugin.ts#OS_MCP_SERVER_ENABLED (OS_MCP_SERVER_ENABLED default-on semantics)", "packages/runtime/src/route-ledger.ts ('* /mcp/**', 'GET /mcp/skill')", "packages/qa/dogfood/test/showcase-mcp-self-connection.dogfood.test.ts (#3167 self-connection pin)", "#3358 §9 (the swept config was off; both sides are now explicit)" @@ -282,9 +282,9 @@ "source": [ "packages/mcp/src/plugin.ts (the three refusal strings + per-call re-resolution, ADR-0101)", "packages/mcp/src/__tests__/plugin.test.ts ('stdio principal admission — fail-closed')", - "packages/runtime/src/domains/mcp.ts (the aggregate bridge routes through callData('aggregate') → the ObjectQL engine so RLS + the FLS aggregate gate always run; the raw driver is deliberately NOT passed)", - "packages/mcp/src/mcp-http-tools.ts (aggregate_records registration: 'Runs under the caller's permissions, row-level security and field-level security'; registered only when the bridge exposes aggregate)", - "packages/plugins/plugin-security/src/security-plugin.test.ts (FLS aggregate-INPUT gate: aggregating OR grouping-by an unreadable field is denied fail-closed with details.forbiddenFields; readable fields aggregate fine)", + "packages/runtime/src/domains/mcp.ts#callData (the aggregate bridge routes through callData('aggregate') → the ObjectQL engine so RLS + the FLS aggregate gate always run; the raw driver is deliberately NOT passed)", + "packages/mcp/src/mcp-http-tools.ts#registered (aggregate_records registration: 'Runs under the caller's permissions, row-level security and field-level security'; registered only when the bridge exposes aggregate)", + "packages/plugins/plugin-security/src/security-plugin.test.ts#fields (FLS aggregate-INPUT gate: aggregating OR grouping-by an unreadable field is denied fail-closed with details.forbiddenFields; readable fields aggregate fine)", "#3358 §9 (verified PASS: fail-closed guard present, reads honor RLS/FLS)", "docs/plans/release-15.1-test-plan.md §G1 (#2976 — aggregate_records走 ENGINE 读路径; RLS/tenant 与 find 一致; FLS 输入门 fail-closed)" ], @@ -357,9 +357,9 @@ ], "traps": ["wrong-persona"], "source": [ - "packages/runtime/src/action-execution.ts (actionAiExposureError exact string; system-object guard; [action-audit] line; shared REST/MCP gate #3915)", - "packages/mcp/src/mcp-http-tools.ts (list_actions/run_action registration + exposed-and-permitted filter)", - "examples/app-showcase/src/ui/actions/index.ts (showcase_portfolio_snapshot — the seeded ai.exposed fixture)", + "packages/runtime/src/action-execution.ts#actionAiExposureError (actionAiExposureError exact string; system-object guard; [action-audit] line; shared REST/MCP gate #3915)", + "packages/mcp/src/mcp-http-tools.ts#list_actions (list_actions/run_action registration + exposed-and-permitted filter)", + "examples/app-showcase/src/ui/actions/index.ts#showcase_portfolio_snapshot (showcase_portfolio_snapshot — the seeded ai.exposed fixture)", "docs/plans/release-15.1-test-plan.md §A9 (#2964) + §G2 (#3010/#3020 standalone action on the bridge)" ], "history": [ @@ -427,7 +427,7 @@ ], "automated": { "kind": "unit", "ref": "packages/mcp/src/mcp-validate-expression.test.ts" }, "source": [ - "packages/mcp/src/mcp-http-tools.ts (validate_expression registration: input schema incl. the 4-value site enum, VALIDATE_SITE_MAP, response shape)", + "packages/mcp/src/mcp-http-tools.ts#VALIDATE_SITE_MAP (validate_expression registration: input schema incl. the 4-value site enum, VALIDATE_SITE_MAP, response shape)", "packages/mcp/src/mcp-validate-expression.test.ts (tier pins: error vs warning vs ok, unknown object, system-object guard)", "#3358 §9 (underlying validateExpression works; MCP path was blocked on the disabled transport)" ], @@ -503,7 +503,7 @@ "source": [ "packages/mcp/src/skill-prompts.ts (#3905 — the projection, its narrowness, and the two-halves boundary)", "packages/mcp/src/skill-prompts.test.ts (list/get/absence/-32602/capability pins)", - "packages/spec/src/ai/skill.zod.ts (instructions served everywhere; tools/surface/triggerConditions cloud-only)", + "packages/spec/src/ai/skill.zod.ts#triggerConditions (instructions served everywhere; tools/surface/triggerConditions cloud-only)", "packages/spec/liveness/skill.json (instructions/name/label/description/active live in-repo via skill-prompts.ts since 2026-08-06)" ], "history": [ @@ -569,7 +569,7 @@ "source": [ "packages/runtime/src/domains/ai.ts (the /ai/agents empty-list courtesy #4058/#4053 + the shared 501 exit)", "packages/runtime/src/domains/unavailable.ts (501-vs-404-vs-503 rationale; message single-sourced from spec)", - "packages/spec/src/system/core-services.zod.ts (REMEDY_DETAIL['ai'] exact sentence; ai slot 'optional')", + "packages/spec/src/system/core-services.zod.ts#REMEDY_DETAIL (REMEDY_DETAIL['ai'] exact sentence; ai slot 'optional')", "packages/runtime/src/route-ledger.ts ('* /ai/**' dynamic row — routes owned by cloud; this repo cannot enumerate them)", "packages/spec/src/ai/model-registry.zod.ts + conversation.zod.ts (schemas exist; no in-repo runtime consumer — the knownGaps basis)" ], @@ -662,7 +662,7 @@ "objectui packages/app-shell/src/console/ConsoleShell.tsx (RequireAiSurface — waits for resolve, splash-preserving redirect, objectui#6507)", "objectui apps/console/src/pages/system/SystemHubPage.tsx (the 'AI Approvals' card built UNconditionally — the K2 gap) + AppContent.tsx (the system/ai-approvals route) + pages/system/AiPendingActionsPage.tsx (thin wrapper, 'Polled every 5 seconds')", "objectui packages/plugin-chatbot/src/usePendingActions.ts,217-300 (cookie-only call(), error → error.message, pollInterval 5000 re-arming regardless of errors) + AiPendingActionsInbox.tsx (destructive alert + the error-blind 'No actions waiting' empty state)", - "packages/runtime/src/domains/ai.ts (#7653 anonymous-deny first; the /ai/agents empty-catalog courtesy #4058/#4053; every other /ai/* → capabilityUnavailable 501) + domains/unavailable.ts (single-sourced remedy sentence)", + "packages/runtime/src/domains/ai.ts#capabilityUnavailable (#7653 anonymous-deny first; the /ai/agents empty-catalog courtesy #4058/#4053; every other /ai/* → capabilityUnavailable 501) + domains/unavailable.ts (single-sourced remedy sentence)", "ai.open-edition-honest-degradation (the API half this item mirrors in the browser — 501 body/discovery parity is proven THERE, not re-proven here)" ], "history": [ diff --git a/docs/qa/platform-checklist/areas/api-backend.json b/docs/qa/platform-checklist/areas/api-backend.json index dfd22d7004..68c6524b9d 100644 --- a/docs/qa/platform-checklist/areas/api-backend.json +++ b/docs/qa/platform-checklist/areas/api-backend.json @@ -70,9 +70,9 @@ "source": [ "#3358 §9", "packages/spec/src/api/batch.zod.ts (ADR-0119 D4)", - "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_NOT_ATOMIC, BATCH_TOO_LARGE, NOT_ATTEMPTED, ROLLED_BACK — #4793)", + "packages/spec/src/api/error-code-ledger.zod.ts#BATCH_NOT_ATOMIC (BATCH_NOT_ATOMIC, BATCH_TOO_LARGE, NOT_ATTEMPTED, ROLLED_BACK — #4793)", "packages/rest/src/rest-route-ledger.ts (batch family)", - "packages/spec/src/api/discovery.zod.ts (transactionalBatch)" + "packages/spec/src/api/discovery.zod.ts#transactionalBatch (transactionalBatch)" ], "history": [ { @@ -419,9 +419,9 @@ "dispatcher-vs-hono-route" ], "source": [ - "packages/spec/src/data/filter.zod.ts (AST_OPERATOR_MAP / FieldOperatorsSchema — the operator variant source)", - "packages/spec/src/data/query.zod.ts (QueryAST keys)", - "packages/metadata-protocol/src/protocol.ts (supported $-params + UNSUPPORTED_QUERY_PARAM #2926 ⑩, INVALID_FIELD #4134)", + "packages/spec/src/data/filter.zod.ts#AST_OPERATOR_MAP (AST_OPERATOR_MAP / FieldOperatorsSchema — the operator variant source)", + "packages/spec/src/data/query.zod.ts#QueryAST (QueryAST keys)", + "packages/metadata-protocol/src/protocol.ts#UNSUPPORTED_QUERY_PARAM (supported $-params + UNSUPPORTED_QUERY_PARAM #2926 ⑩, INVALID_FIELD #4134)", "#5869 (commit 10c4ea9)", "release-15.1 plan §A6", "examples/app-showcase/src/data/seed/index.ts" @@ -521,7 +521,7 @@ ], "source": [ "packages/spec/src/api/error-code-ledger.zod.ts (ADR-0112 D3)", - "packages/spec/src/api/errors.zod.ts (StandardErrorCode, FieldErrorCode, HttpStatusErrorCodeMap)", + "packages/spec/src/api/errors.zod.ts#StandardErrorCode (StandardErrorCode, FieldErrorCode, HttpStatusErrorCodeMap)", "packages/spec/src/shared/error-map.zod.ts", "packages/rest/src/rest-route-ledger.ts (sampled families)", "ADR-0114 D2/D4" @@ -627,7 +627,7 @@ "source": [ "packages/rest/src/rest-route-ledger.ts (batch family — the four doors)", "packages/spec/src/api/batch.zod.ts", - "packages/spec/src/api/error-code-ledger.zod.ts (BATCH_TOO_LARGE, ERR_BULK_RESULT_MISMATCH)", + "packages/spec/src/api/error-code-ledger.zod.ts#BATCH_TOO_LARGE (BATCH_TOO_LARGE, ERR_BULK_RESULT_MISMATCH)", "#2982", "release-15.1 plan §A3/A4" ], @@ -782,20 +782,20 @@ "absence-inference" ], "source": [ - "packages/rest/src/rest-route-ledger.ts (variant source — the 19 REST families)", + "packages/rest/src/rest-route-ledger.ts#REST (variant source — the 19 REST families)", "packages/rest/src/rest-route-ledger.conformance.test.ts", - "packages/runtime/src/route-ledger.ts (dispatcher ledger — share-links/keys/notifications/suggested-bindings/i18n/analytics families + the meta.getLegalNextStates state route)", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (AUTH_ROUTE_LEDGER — the enumerated better-auth table, #3656; raw-app mount)", + "packages/runtime/src/route-ledger.ts#getLegalNextStates (dispatcher ledger — share-links/keys/notifications/suggested-bindings/i18n/analytics families + the meta.getLegalNextStates state route)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#AUTH_ROUTE_LEDGER (AUTH_ROUTE_LEDGER — the enumerated better-auth table, #3656; raw-app mount)", "packages/services/service-storage/src/storage-route-ledger.ts + packages/services/service-i18n/src/i18n-route-ledger.ts (tranche-3 per-service ledgers, #3636)", "packages/services/service-settings/src/settings-route-ledger.ts (#7526 — the /api/settings family, ledgered; guarded by the parity dogfood gate, no per-package test by design)", "packages/services/service-datasource/src/datasource-route-ledger.ts + datasource-route-ledger.conformance.test.ts (#7744 — the admin family, ledgered)", - "packages/services/service-datasource/src/admin-routes.ts (the #9391/#9593 manage_platform_settings floor on all eleven routes, drivers catalog included)", + "packages/services/service-datasource/src/admin-routes.ts#manage_platform_settings (the #9391/#9593 manage_platform_settings floor on all eleven routes, drivers catalog included)", "packages/metadata/src/metadata-route-ledger.ts (metadata HMR — raw-app mount, dev-only posture #12140/#5673)", - "packages/triggers/trigger-api/src/trigger-api-route-ledger.ts (#11863 — POST /api/v1/automation/hooks/:flowName/:hookId, raw-app mount)", + "packages/triggers/trigger-api/src/trigger-api-route-ledger.ts#flowName (#11863 — POST /api/v1/automation/hooks/:flowName/:hookId, raw-app mount)", "packages/cloud-connection/src/cloud-connection-route-ledger.ts (#11882 — 16 rows / four registrar families, raw-app mounts unfindable by the parity gate by construction)", "packages/cli/src/utils/console-route-ledger.ts (#11882 — the four static-asset rows)", - "packages/qa/dogfood/test/route-ledger-live-mount-parity.dogfood.test.ts (#7526 — the parity gate whose getMountedRoutes() blind spot the raw-app clause covers)", - "examples/app-showcase/src/data/objects/task.object.ts (task_status_flow transitions for the meta state-route clause)", + "packages/qa/dogfood/test/route-ledger-live-mount-parity.dogfood.test.ts#getMountedRoutes (#7526 — the parity gate whose getMountedRoutes() blind spot the raw-app clause covers)", + "examples/app-showcase/src/data/objects/task.object.ts#task_status_flow (task_status_flow transitions for the meta state-route clause)", "#3587", "#3361" ], @@ -979,10 +979,10 @@ "dispatcher-vs-hono-route" ], "source": [ - "packages/runtime/src/domains/packages.ts (POST install: pkgId-required 400, 409-duplicate guard + overwrite opt-in #2995; PATCH partial-patch validators; routes through protocol.installPackage / protocol.updatePackage)", + "packages/runtime/src/domains/packages.ts#pkgId (POST install: pkgId-required 400, 409-duplicate guard + overwrite opt-in #2995; PATCH partial-patch validators; routes through protocol.installPackage / protocol.updatePackage)", "packages/runtime/src/route-ledger.ts (POST /packages → packages.install, PATCH /packages/:id → packages.update, GET /packages → packages.list)", "packages/rest/src/rest-route-ledger.ts (POST /api/v1/packages/publish moved off the bare POST /packages in #3610; GET/DELETE /packages/:id direct-mount shadows)", - "packages/spec/src/api/errors.zod.ts (HttpStatusErrorCodeMap 409 → RESOURCE_CONFLICT)", + "packages/spec/src/api/errors.zod.ts#HttpStatusErrorCodeMap (HttpStatusErrorCodeMap 409 → RESOURCE_CONFLICT)", "PENDING-GAPS §G3 (#2995 dup-clobber, #2971, #3007)" ], "history": [ @@ -1065,7 +1065,7 @@ "source": [ "objectui apps/console/src/pages/developer/ApiConsolePage.tsx (the API console: raw fetch to `${client.baseUrl}${url}`, status/duration/JSON pane, request history)", "objectui apps/console/src/pages/developer/hooks/useApiDiscovery.ts (tree built from GET /api/v1/discovery services/routes + client.meta types/objects; isServiceUsable gate per ADR-0076 D12)", - "packages/rest/src/rest-route-ledger.ts (the REST families the tree is spot-checked against)", + "packages/rest/src/rest-route-ledger.ts#REST (the REST families the tree is spot-checked against)", "packages/spec/src/api/discovery.zod.ts (the discovery payload)" ], "history": [ @@ -1157,10 +1157,10 @@ }, "traps": ["silent-coercion"], "source": [ - "packages/spec/src/data/filter-comparand-type.ts (ACCEPTED_FILTER_COMPARAND_TYPES, ACCEPTED_FILTER_COMPARAND_TYPES_SENTENCE, FILTER_COMPARAND_BIGINT_EXACT_LIMIT, isAcceptedFilterComparand, normalizeFilterComparandTypes)", + "packages/spec/src/data/filter-comparand-type.ts#ACCEPTED_FILTER_COMPARAND_TYPES (ACCEPTED_FILTER_COMPARAND_TYPES, ACCEPTED_FILTER_COMPARAND_TYPES_SENTENCE, FILTER_COMPARAND_BIGINT_EXACT_LIMIT, isAcceptedFilterComparand, normalizeFilterComparandTypes)", "packages/spec/src/data/filter-comparand-shape.ts", "packages/spec/src/data/filter-comparand-type-conformance.ts", - "packages/spec/src/data/filter-dotted-head.ts (classifyDottedFilterHead, DottedFilterHeadClass)", + "packages/spec/src/data/filter-dotted-head.ts#classifyDottedFilterHead (classifyDottedFilterHead, DottedFilterHeadClass)", "packages/spec/src/data/filter-subtree-provenance.ts", "packages/spec/src/migrations/entries/semantic/18.engine-dotted-filter-refused.ts", "packages/spec/src/migrations/entries/semantic/17.engine-dotted-projection-refused.ts", @@ -1261,7 +1261,7 @@ }, "traps": ["clock-skew", "timezone-boundary"], "source": [ - "packages/spec/src/data/date-range-presets.ts (DATE_RANGE_PRESETS, DATE_RANGE_PRESET_MACRO_WINDOWS, isDateRangePresetName, bareDateRangePresetComparandMessage)", + "packages/spec/src/data/date-range-presets.ts#DATE_RANGE_PRESETS (DATE_RANGE_PRESETS, DATE_RANGE_PRESET_MACRO_WINDOWS, isDateRangePresetName, bareDateRangePresetComparandMessage)", "packages/spec/src/migrations/entries/semantic/18.filter-preset-ordering-comparand-refused.ts", "packages/spec/src/data/temporal-comparand.ts + packages/core/src/utils/temporal-comparand.ts", "packages/objectql/src/temporal-comparand-door.ts" @@ -1451,10 +1451,10 @@ "first-boot-cold-start" ], "source": [ - "packages/runtime/src/domains/actions.ts (the REST door's ordered gates: ADR-0066 D4 capability gate → activation consult → type branch → ADR-0104 param contract → best-effort record load)", - "packages/runtime/src/action-execution.ts (disabledActionRefusal — ACTION_DISABLED_CODE/STATUS 409, the shared consult both doors call; invokeBusinessAction — the MCP door, whose AI-exposure + capability gates run ahead of the consult)", - "packages/objectql/src/engine.ts (isActionEnabled / describeDisabledAction / hydrateActionActivations / setActionActive — projection over the durable ledger, row-first then projection)", - "packages/spec/src/api/error-code-ledger.zod.ts (ACTION_DISABLED registered — the *_DISABLED family)", + "packages/runtime/src/domains/actions.ts#record (the REST door's ordered gates: ADR-0066 D4 capability gate → activation consult → type branch → ADR-0104 param contract → best-effort record load)", + "packages/runtime/src/action-execution.ts#disabledActionRefusal (disabledActionRefusal — ACTION_DISABLED_CODE/STATUS 409, the shared consult both doors call; invokeBusinessAction — the MCP door, whose AI-exposure + capability gates run ahead of the consult)", + "packages/objectql/src/engine.ts#isActionEnabled (isActionEnabled / describeDisabledAction / hydrateActionActivations / setActionActive — projection over the durable ledger, row-first then projection)", + "packages/spec/src/api/error-code-ledger.zod.ts#ACTION_DISABLED (ACTION_DISABLED registered — the *_DISABLED family)", "examples/app-showcase/src/ui/actions/index.ts + predicate-matrix.action.ts (the four probe actions)", "docs/adr/0126-packaged-metadata-customization-model.md (§8 item 2, §4 absence-means-active, §6 wall 3)", "#12438", @@ -1569,10 +1569,10 @@ "auth-state-leak" ], "source": [ - "packages/runtime/src/domains/actions.ts (ACTION_ACTIVATION_SEGMENT, handleActionActivationWrite — the five ordered steps: authority → shape → body → declaration/ambiguity → durable write; refuseAmbiguousActionActivation)", - "packages/runtime/src/domains/activation-gate.ts (refuseUngrantedActivationAuthoring — manage_metadata tier, #10243 sentence; refuseUngrantedActivationWrite — the §5 posture tier; ACTION_ACTIVATION_SUBJECT's no-clone remedy)", - "packages/runtime/src/route-ledger.ts (the #12160 server-only row: servedBy reconstruction through /actions/:object/:action/:recordId, the 4-segment router-404, and why the segment is reserved)", - "packages/objectql/src/engine.ts (setActionActive — durable row first, projection second; the 503-on-no-ledger contract)", + "packages/runtime/src/domains/actions.ts#ACTION_ACTIVATION_SEGMENT (ACTION_ACTIVATION_SEGMENT, handleActionActivationWrite — the five ordered steps: authority → shape → body → declaration/ambiguity → durable write; refuseAmbiguousActionActivation)", + "packages/runtime/src/domains/activation-gate.ts#refuseUngrantedActivationAuthoring (refuseUngrantedActivationAuthoring — manage_metadata tier, #10243 sentence; refuseUngrantedActivationWrite — the §5 posture tier; ACTION_ACTIVATION_SUBJECT's no-clone remedy)", + "packages/runtime/src/route-ledger.ts#servedBy (the #12160 server-only row: servedBy reconstruction through /actions/:object/:action/:recordId, the 4-segment router-404, and why the segment is reserved)", + "packages/objectql/src/engine.ts#setActionActive (setActionActive — durable row first, projection second; the 503-on-no-ledger contract)", "sibling item api-backend.packaged-action-disabled-dispatch (what DISPATCH does with the row this door writes) · access-security.activation-write-operator-gate owns the gate's posture depths", "docs/adr/0126-packaged-metadata-customization-model.md (§4, §5, §6 walls, §8 item 2)", "#12438", @@ -1687,12 +1687,12 @@ "wrong-persona" ], "source": [ - "packages/spec/src/data/query.zod.ts (AggregationFunction + ARRAY_AGG_RETIRED/STRING_AGG_RETIRED error map), (DateGranularity), GroupByNodeSchema (dateGranularity spelling)", - "packages/objectql/src/in-memory-aggregation.ts (the six function arms incl. #11152 boolean-as-number and the #10576 per-aggregation filter; bucketDateValue ISO shapes; the #3839 real-null bucket rule at the module head)", - "packages/spec/src/data/aggregation-policy.ts (emptyGroupValueFor — the ruled empty-set identities; objectui#3136)", - "packages/runtime/src/action-execution.ts (the aggregate door: engine-only, at-least-one-aggregation, the raw-rows degradation hazard)", - "packages/rest/src/rest-server.ts (POST /data/:object/query — the wire door that accepts groupBy/aggregations)", - "packages/metadata-protocol/src/protocol.ts (assertAggregationFieldsExist — the located unknown-field refusal)", + "packages/spec/src/data/query.zod.ts#AggregationFunction (AggregationFunction + ARRAY_AGG_RETIRED/STRING_AGG_RETIRED error map), (DateGranularity), GroupByNodeSchema (dateGranularity spelling)", + "packages/objectql/src/in-memory-aggregation.ts#bucketDateValue (the six function arms incl. #11152 boolean-as-number and the #10576 per-aggregation filter; bucketDateValue ISO shapes; the #3839 real-null bucket rule at the module head)", + "packages/spec/src/data/aggregation-policy.ts#emptyGroupValueFor (emptyGroupValueFor — the ruled empty-set identities; objectui#3136)", + "packages/runtime/src/action-execution.ts#engine (the aggregate door: engine-only, at-least-one-aggregation, the raw-rows degradation hazard)", + "packages/rest/src/rest-server.ts#data (POST /data/:object/query — the wire door that accepts groupBy/aggregations)", + "packages/metadata-protocol/src/protocol.ts#assertAggregationFieldsExist (assertAggregationFieldsExist — the located unknown-field refusal)", "#10576, #11152, #3839, #3773" ], "history": [ @@ -1784,8 +1784,8 @@ "timezone-boundary" ], "source": [ - "packages/formula/src/stdlib.ts (registerStdLib — the 27 registerFunction calls; calendarDayUtc ADR-0053 D1; addMonthsUtc clamp; the floor/ceil #3306 note), (registerNumericCoercions #1928)", - "packages/objectql/src/engine.ts (applyFormulaPlan — evaluate-fault → null, the silent-null contract; the evaluateFormulaField docblock states the compile-throws vs evaluate-nulls split)", + "packages/formula/src/stdlib.ts#registerStdLib (registerStdLib — the 27 registerFunction calls; calendarDayUtc ADR-0053 D1; addMonthsUtc clamp; the floor/ceil #3306 note), (registerNumericCoercions #1928)", + "packages/objectql/src/engine.ts#applyFormulaPlan (applyFormulaPlan — evaluate-fault → null, the silent-null contract; the evaluateFormulaField docblock states the compile-throws vs evaluate-nulls split)", "content/docs/data-modeling/formulas.mdx (the advertised catalog the matrix cross-checks)", "examples/app-showcase/src/data/objects/field-zoo.object.ts + project.object.ts (the stock live-leg formulas)", "ADR-0053 Phase 2 D1 (calendar-day timezone contract)", @@ -1903,11 +1903,11 @@ "auth-state-leak" ], "source": [ - "packages/spec/src/data/object.zod.ts (ApiMethod — the six primitives), (LEGACY_API_METHODS + LEGACY_API_METHOD_GUIDANCE), (stripLegacyApiMethods — strip-and-warn + the deny-all cliff), (the z.preprocess wiring)", - "packages/spec/src/data/api-derivation.ts (resolveEffectiveApiMethods three-state, isApiOperationAllowed bulk∧child + writeMode precision, effectiveOperationsArray)", - "packages/rest/src/rest-server.ts (apiAccessDenialFromEnable — the 404/405 fork + effective allowed[]), (enforceApiAccess — external boundary only, default-allow, #3770 unknown-object pass-through), (writeMode-precise import), (batch per-op before txn), (bulk doors with bulkChild)", - "packages/objectql/src/registry.ts (warnStrippedLegacyApiMethods — the per-object registration diagnostic)", - "packages/spec/src/api/error-code-ledger.zod.ts (OBJECT_API_DISABLED, OBJECT_API_METHOD_NOT_ALLOWED)", + "packages/spec/src/data/object.zod.ts#ApiMethod (ApiMethod — the six primitives), (LEGACY_API_METHODS + LEGACY_API_METHOD_GUIDANCE), (stripLegacyApiMethods — strip-and-warn + the deny-all cliff), (the z.preprocess wiring)", + "packages/spec/src/data/api-derivation.ts#resolveEffectiveApiMethods (resolveEffectiveApiMethods three-state, isApiOperationAllowed bulk∧child + writeMode precision, effectiveOperationsArray)", + "packages/rest/src/rest-server.ts#apiAccessDenialFromEnable (apiAccessDenialFromEnable — the 404/405 fork + effective allowed[]), (enforceApiAccess — external boundary only, default-allow, #3770 unknown-object pass-through), (writeMode-precise import), (batch per-op before txn), (bulk doors with bulkChild)", + "packages/objectql/src/registry.ts#warnStrippedLegacyApiMethods (warnStrippedLegacyApiMethods — the per-object registration diagnostic)", + "packages/spec/src/api/error-code-ledger.zod.ts#OBJECT_API_DISABLED (OBJECT_API_DISABLED, OBJECT_API_METHOD_NOT_ALLOWED)", "packages/platform-objects/src/identity/sys-session.object.ts + sys-device-code.object.ts + sys-api-key.object.ts + sys-oauth-consent.object.ts (the stock narrowed objects)", "ADR-0049 / #1889 (shipping a non-enforcing apiEnabled is false security), #3391 (three-state), #3543 (enum shrink + strip), #3545 (non-array fails closed), #7912 (apiExposureDenialReason order)", "sibling clause: identity-auth.session-list-revoke (the sys_session 405-before-403 precedent this item generalizes)" diff --git a/docs/qa/platform-checklist/areas/approvals.json b/docs/qa/platform-checklist/areas/approvals.json index 6e52a1261d..0b70db966f 100644 --- a/docs/qa/platform-checklist/areas/approvals.json +++ b/docs/qa/platform-checklist/areas/approvals.json @@ -78,7 +78,7 @@ "traps": ["automation-input", "wrong-persona"], "source": [ "#3358 §1", "#3409", "#3411", - "examples/app-showcase/src/automation/flows/index.ts (ExpenseSignoffFlow, behavior per_group)", + "examples/app-showcase/src/automation/flows/index.ts#ExpenseSignoffFlow (ExpenseSignoffFlow, behavior per_group)", "examples/app-showcase/src/security/seed-approval-demo.ts (distinct-holder wiring + submitter stamping)", "packages/spec/src/automation/approval.zod.ts (behavior enum + group labels, #3266)" ], @@ -157,9 +157,9 @@ "traps": ["seed-data-thin", "wrong-persona"], "source": [ "#3358 §1", - "examples/app-showcase/src/automation/flows/index.ts (CommitteeQuorumFlow, #3266)", - "packages/spec/src/automation/approval.zod.ts (behavior 'quorum' + minApprovals clamp: 'Clamped at runtime so it can never exceed the resolvable approver count')", - "examples/app-showcase/src/security/seed-approval-demo.ts (EXP-DEMO launch + admin position grants)" + "examples/app-showcase/src/automation/flows/index.ts#CommitteeQuorumFlow (CommitteeQuorumFlow, #3266)", + "packages/spec/src/automation/approval.zod.ts#minApprovals (behavior 'quorum' + minApprovals clamp: 'Clamped at runtime so it can never exceed the resolvable approver count')", + "examples/app-showcase/src/security/seed-approval-demo.ts#admin (EXP-DEMO launch + admin position grants)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; carried the fixture blocker forward explicitly", "ref": "#3358" }, @@ -377,7 +377,7 @@ "#3358 §1", "#7213", "#7233", "objectui apps/console/src/pages/system/ApprovalsInboxPage.tsx (#2678 P1.5 — 'notifications carry /system/approvals?request=')", - "packages/plugins/plugin-approvals/src/approval-service.ts (ApprovalService.notify — the central /system/approvals → ?request= rewrite; the emitting topic set, which has no approval.opened member)" + "packages/plugins/plugin-approvals/src/approval-service.ts#ApprovalService (ApprovalService.notify — the central /system/approvals → ?request= rewrite; the emitting topic set, which has no approval.opened member)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358 (verified twice on fresh loads)", "ref": "#3358" }, @@ -491,12 +491,12 @@ "source": [ "#3358 §1 (the action-table evidence this matrix grounds in)", "packages/rest/src/rest-route-ledger.ts (approve/reject/recall/revise/resubmit/reassign/remind/request-info/comment + GET /:id/actions)", - "packages/rest/src/rest-server.ts (flowMoveRoute: revise=pending approver, resubmit=submitter; threadRoute access per action; recall submitter-only)", - "packages/plugins/plugin-approvals/src/approval-service.ts (ApprovalService.resubmit — 'traversal walks the declared back-edge into the approval node, whose executor opens the round-N+1 request'; the resubmitted row's status is never rewritten. sendBack counts prior rounds as returned siblings on flow_run_id + flow_node_id)", - "packages/spec/src/contracts/approval-service.ts (APPROVAL_STATUSES: pending|approved|rejected|recalled|returned)", - "examples/app-showcase/src/automation/flows/index.ts (BudgetApprovalFlow — ADR-0044 revise loop, maxRevisions 2, exec step without a revise edge, lockRecord pair)", - "packages/plugins/plugin-approvals/src/approval-service.ts (readBackRequest — the write path's post-mutation echo; #12769 docblock: org-less rows are a live state, the null echo used to escape behind the callers' fresh! non-null assertions, now it refuses loudly; landed as PR #13181)", - "packages/rest/src/rest-server.ts (handleApprovalError prefix map — READ_BACK_FAILED deliberately unmapped, so it reaches the wire as the generic 500 APPROVAL__FAILED envelope carrying the named text)" + "packages/rest/src/rest-server.ts#flowMoveRoute (flowMoveRoute: revise=pending approver, resubmit=submitter; threadRoute access per action; recall submitter-only)", + "packages/plugins/plugin-approvals/src/approval-service.ts#ApprovalService (ApprovalService.resubmit — 'traversal walks the declared back-edge into the approval node, whose executor opens the round-N+1 request'; the resubmitted row's status is never rewritten. sendBack counts prior rounds as returned siblings on flow_run_id + flow_node_id)", + "packages/spec/src/contracts/approval-service.ts#APPROVAL_STATUSES (APPROVAL_STATUSES: pending|approved|rejected|recalled|returned)", + "examples/app-showcase/src/automation/flows/index.ts#BudgetApprovalFlow (BudgetApprovalFlow — ADR-0044 revise loop, maxRevisions 2, exec step without a revise edge, lockRecord pair)", + "packages/plugins/plugin-approvals/src/approval-service.ts#readBackRequest (readBackRequest — the write path's post-mutation echo; #12769 docblock: org-less rows are a live state, the null echo used to escape behind the callers' fresh! non-null assertions, now it refuses loudly; landed as PR #13181)", + "packages/rest/src/rest-server.ts#handleApprovalError (handleApprovalError prefix map — READ_BACK_FAILED deliberately unmapped, so it reaches the wire as the generic 500 APPROVAL__FAILED envelope carrying the named text)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial — decision-action matrix derived from the approvals REST route ledger and the ADR-0044 revise/resubmit flow shape", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -557,9 +557,9 @@ "traps": ["dispatcher-vs-hono-route"], "automated": { "kind": "test", "ref": "packages/services/service-automation/src/resume-authority-gate.test.ts" }, "source": [ - "packages/runtime/src/route-ledger.ts (the resume route's #3801 note: resumeAuthority 'service' → 403, decisions via ApprovalService.decide)", - "examples/app-showcase/src/automation/flows/index.ts (InvoiceDualSignoffFlow — documents the exact 403 + approvals-API sequence)", - "packages/spec/src/automation/approval.zod.ts (APPROVAL_NODE_TYPE; ADR-0039 Track A aggregating node)" + "packages/runtime/src/route-ledger.ts#resumeAuthority (the resume route's #3801 note: resumeAuthority 'service' → 403, decisions via ApprovalService.decide)", + "examples/app-showcase/src/automation/flows/index.ts#InvoiceDualSignoffFlow (InvoiceDualSignoffFlow — documents the exact 403 + approvals-API sequence)", + "packages/spec/src/automation/approval.zod.ts#APPROVAL_NODE_TYPE (APPROVAL_NODE_TYPE; ADR-0039 Track A aggregating node)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial — pins the #3801 resume-authority gate as a both-sides item (approval 403 + screen 2xx contrast)", "ref": "claude/platform-test-checklist-ocwugl" } @@ -633,9 +633,9 @@ "traps": ["automation-input", "stale-console-bundle"], "source": [ "examples/app-showcase/src/automation/flows/dynamic-approval.flow.ts (#3447 P2)", - "packages/spec/src/automation/approval.zod.ts (DecisionOutputDefSchema — typed pickers, required-to-approve objectui#2955; expression approvers + resolveAs)", + "packages/spec/src/automation/approval.zod.ts#DecisionOutputDefSchema (DecisionOutputDefSchema — typed pickers, required-to-approve objectui#2955; expression approvers + resolveAs)", "examples/app-showcase/src/automation/flows/approver-bindings.flow.ts (#3508 — the degraded-to-free-text failure the typed control fixes)", - "packages/services/service-automation/src/engine.ts (the two `status: 'paused'` recordLog calls — the suspend path records no variables, which is why the vars clause needs a functional oracle)" + "packages/services/service-automation/src/engine.ts#recordLog (the two `status: 'paused'` recordLog calls — the suspend path records no variables, which is why the vars clause needs a functional oracle)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial — covers the #3447 dynamic-routing chain (typed decision outputs → vars → expression approvers) with the required-output gate", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -715,11 +715,11 @@ "traps": ["wrong-persona", "seed-data-thin"], "source": [ "packages/plugins/plugin-approvals/src/sys-approval-delegation.object.ts (#1322 M1 — self-service OOO rule, half-open UTC window, resolution-time enforcement)", - "packages/plugins/plugin-approvals/src/approval-service.ts (applyOooDelegation + lookupActiveDelegation — individually-routed only; M4 ooo_substitute audit row + approval.ooo_substituted / approval.ooo_skipped notifications)", + "packages/plugins/plugin-approvals/src/approval-service.ts#applyOooDelegation (applyOooDelegation + lookupActiveDelegation — individually-routed only; M4 ooo_substitute audit row + approval.ooo_substituted / approval.ooo_skipped notifications)", "@objectstack/core isGrantActive (ADR-0091 D2 half-open validity predicate)", "examples/app-showcase/src/automation/flows/approver-bindings.flow.ts (the {type:'user'|'manager'|'field'} approver specimens the scratch flow is shaped after)", - "packages/plugins/plugin-approvals/src/lifecycle-hooks.ts (bindDelegationWriteGuard — self-service-only, no admin exemption since #4839; the reason the old third-party persona shape was unconstructible)", - "examples/app-showcase/src/security/seed-approval-demo.ts (AUDITOR_DEMO_USER — the second real persona B is provisioned from)" + "packages/plugins/plugin-approvals/src/lifecycle-hooks.ts#bindDelegationWriteGuard (bindDelegationWriteGuard — self-service-only, no admin exemption since #4839; the reason the old third-party persona shape was unconstructible)", + "examples/app-showcase/src/security/seed-approval-demo.ts#AUDITOR_DEMO_USER (AUDITOR_DEMO_USER — the second real persona B is provisioned from)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "initial — pins the #1322 OOO delegation reroute: active A→B window reroutes an individually-routed slot to B (audited + notified, decided under B's own identity); expiry hands it back to A at resolution time", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -796,7 +796,7 @@ "objectui packages/app-shell/src/views/RecordDetailView.tsx (buildApprovalDecisionActions — record_header Approve/Reject, single-dialog Reject #3126, actionParams comment #2955; approvalLocked/canEdit gating #2902)", "objectui packages/app-shell/src/hooks/useRecordApprovals.ts (recordLockedByApproval, canDecide, /approvals/requests decide routes)", "objectui packages/app-shell/src/views/RecordDetailView.approvalDecisionActions.test.tsx (the param-contract pin)", - "examples/app-showcase/src/automation/flows/index.ts (BudgetApprovalFlow — manager_review lockRecord:false / exec_review lockRecord:true on showcase_project)" + "examples/app-showcase/src/automation/flows/index.ts#BudgetApprovalFlow (BudgetApprovalFlow — manager_review lockRecord:false / exec_review lockRecord:true on showcase_project)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "initial — the record-page approval surface (distinct from the inbox-drawer items): header Approve/Reject, one-dialog Reject (#3126), decision round-trip, and the lockRecord-driven inline-edit hide (#2902)", "ref": "claude/platform-test-checklist-ocwugl" } @@ -873,8 +873,8 @@ ], "traps": ["seed-data-thin", "stale-dist"], "source": [ - "packages/spec/src/automation/approval.zod.ts (ApprovalEscalationSchema — enabled/timeoutHours(min 1)/action(reassign|auto_approve|auto_reject|notify)/escalateTo/notifySubmitter, strict, #4001; carried on the approval node as config.escalation)", - "packages/plugins/plugin-approvals/src/approval-service.ts (runEscalations sweep + escalateRequest — audit-first escalate row, per-action effects, notifySubmitter; slaDueAt; ESCALATION_JOB_NAME ADR-0042; this.clock)", + "packages/spec/src/automation/approval.zod.ts#ApprovalEscalationSchema (ApprovalEscalationSchema — enabled/timeoutHours(min 1)/action(reassign|auto_approve|auto_reject|notify)/escalateTo/notifySubmitter, strict, #4001; carried on the approval node as config.escalation)", + "packages/plugins/plugin-approvals/src/approval-service.ts#runEscalations (runEscalations sweep + escalateRequest — audit-first escalate row, per-action effects, notifySubmitter; slaDueAt; ESCALATION_JOB_NAME ADR-0042; this.clock)", "packages/plugins/plugin-approvals/src/sys-approval-request.object.ts (sla_due_at surfaced on the request)" ], "history": [ @@ -959,8 +959,8 @@ "traps": ["stale-console-bundle", "hydration-race", "wrong-persona"], "source": [ "#7213 (entry convergence epic)", "#7231", "#7234", "#7331 (this item's browser run)", - "packages/platform-objects/src/apps/account.app.ts (the Approvals nav entry, type component / componentRef approvals:inbox)", - "packages/platform-objects/src/apps/account-approvals-nav.test.ts (pins the entry shape; explicitly does NOT assert main.tsx's side-effect import)", + "packages/platform-objects/src/apps/account.app.ts#componentRef (the Approvals nav entry, type component / componentRef approvals:inbox)", + "packages/platform-objects/src/apps/account-approvals-nav.test.ts#entry (pins the entry shape; explicitly does NOT assert main.tsx's side-effect import)", "objectui apps/console/src/main.tsx + the approvals:inbox registration (objectui#4071)" ], "history": [ @@ -1016,7 +1016,7 @@ "traps": ["hydration-race", "stale-console-bundle"], "source": [ "#7213", "#7268", "#7331 (this item's browser run)", - "packages/plugins/plugin-approvals/src/approvals-plugin.ts (navigationContributions → setup group_approvals)", + "packages/plugins/plugin-approvals/src/approvals-plugin.ts#navigationContributions (navigationContributions → setup group_approvals)", "packages/plugins/plugin-approvals/src/nav-contribution.test.ts" ], "history": [ @@ -1161,11 +1161,11 @@ ], "traps": ["dispatcher-vs-hono-route", "auth-state-leak"], "source": [ - "packages/plugins/plugin-approvals/src/approvals-plugin.ts (the raw Hono mount at /api/v1/approvals/act — GET renders via peekActionToken, POST redeems; mounted on kernel:ready off the canonical http.server handle, #4251 B5)", - "packages/plugins/plugin-approvals/src/approval-service.ts (actionLinkUrl · issueActionTokens: hashed-at-rest + FORBIDDEN for non-pending approvers · resolveActionToken: invalid/consumed/expired/not_pending/not_approver chain · peekActionToken 'validate WITHOUT consuming' · redeemActionToken 'consume the token FIRST … then decide as the bound approver', #3783 the-token-IS-the-authentication attribution); ACTION_TOKEN_TTL_MS = 72h", - "packages/plugins/plugin-approvals/src/approval-service.ts (the remind fan-out is the mint path: per-approver Approve/Reject action links on topic approval.reminder; literal slots fall back to a tokenless nudge)", - "packages/plugins/plugin-approvals/src/action-link-pages.ts (ADR-0043 session-less bilingual pages; 'The GET page NEVER mutates'; RESULT_COPY — which refusal shapes carry summaryRows and which do not)", - "packages/services/service-messaging/src/messaging-service.ts + (writeEvent persists the payload on sys_notification — the stock-boot token-capture seam this item's fixtures rest on)", + "packages/plugins/plugin-approvals/src/approvals-plugin.ts#http (the raw Hono mount at /api/v1/approvals/act — GET renders via peekActionToken, POST redeems; mounted on kernel:ready off the canonical http.server handle, #4251 B5)", + "packages/plugins/plugin-approvals/src/approval-service.ts#actionLinkUrl (actionLinkUrl · issueActionTokens: hashed-at-rest + FORBIDDEN for non-pending approvers · resolveActionToken: invalid/consumed/expired/not_pending/not_approver chain · peekActionToken 'validate WITHOUT consuming' · redeemActionToken 'consume the token FIRST … then decide as the bound approver', #3783 the-token-IS-the-authentication attribution); ACTION_TOKEN_TTL_MS = 72h", + "packages/plugins/plugin-approvals/src/approval-service.ts#approver (the remind fan-out is the mint path: per-approver Approve/Reject action links on topic approval.reminder; literal slots fall back to a tokenless nudge)", + "packages/plugins/plugin-approvals/src/action-link-pages.ts#RESULT_COPY (ADR-0043 session-less bilingual pages; 'The GET page NEVER mutates'; RESULT_COPY — which refusal shapes carry summaryRows and which do not)", + "packages/services/service-messaging/src/messaging-service.ts#writeEvent + (writeEvent persists the payload on sys_notification — the stock-boot token-capture seam this item's fixtures rest on)", "packages/plugins/plugin-approvals/src/sys-approval-token.object.ts (the token store)" ], "history": [ @@ -1253,8 +1253,8 @@ ], "traps": ["seed-data-thin", "wrong-persona"], "source": [ - "packages/spec/src/automation/approval.zod.ts (ApproverType, 10 members incl. deprecated role + dead queue), (DEPRECATED_APPROVER_TYPES role→org_membership_level + canonicalApproverType), (NON_AUTHORABLE_APPROVER_TYPES), (onEmptyApprovers admin_rescue|fail|auto_approve, default admin_rescue)", - "packages/plugins/plugin-approvals/src/approval-service.ts (expandApprovers — per-spec group tagging; expression resolved outside the flat contract), (resolveApproverSpec — role warn, per-type branches, queue warn, #3807 expanded-to-nobody warn, literal type:value fallback), (expandBusinessUnitUsers — active-descendant BFS + membership union), (the empty-slate fork: 'empty' = no concrete person, NO_APPROVERS / autoApproved / admin_rescue warn)", + "packages/spec/src/automation/approval.zod.ts#ApproverType (ApproverType, 10 members incl. deprecated role + dead queue), (DEPRECATED_APPROVER_TYPES role→org_membership_level + canonicalApproverType), (NON_AUTHORABLE_APPROVER_TYPES), (onEmptyApprovers admin_rescue|fail|auto_approve, default admin_rescue)", + "packages/plugins/plugin-approvals/src/approval-service.ts#expandApprovers (expandApprovers — per-spec group tagging; expression resolved outside the flat contract), (resolveApproverSpec — role warn, per-type branches, queue warn, #3807 expanded-to-nobody warn, literal type:value fallback), (expandBusinessUnitUsers — active-descendant BFS + membership union), (the empty-slate fork: 'empty' = no concrete person, NO_APPROVERS / autoApproved / admin_rescue warn)", "examples/app-showcase/src/automation/flows/approver-bindings.flow.ts (the #3508 DESIGNER specimen — draft, empty values, its docblock is the evidence the runtime legs need scratch flows)", "examples/app-showcase/src/data/seed/index.ts (BU tree seeded; membership is a runtime admin action by design)", "#3424 (privileged override — the admin_rescue escape hatch), #3447 P2, #3508, #3807, ADR-0090 D3" @@ -1333,10 +1333,10 @@ "traps": ["seed-data-thin", "eventual-consistency"], "automated": { "kind": "test", "ref": "packages/plugins/plugin-approvals/src/status-mirror-cascade.integration.test.ts" }, "source": [ - "packages/spec/src/automation/approval.zod.ts (approvalStatusField — 'Should be readonly on the object. Omitted ⇒ status is exposed only via sys_approval_request'; the docblock's four-value list, which the service's fifth value 'returned' outgrew)", - "packages/plugins/plugin-approvals/src/approval-service.ts (mirrorStatusField — elevated but not anonymous #3783: system write carrying the actor's userId; actorId null only for machine transitions; warn-not-throw on failure) + the six call sites (dead-run sweep, deliberately user-less)", - "packages/plugins/plugin-approvals/src/lifecycle-hooks.ts (the record lock's whitelist: 'block, EXCEPT when the only changed field is the configured approvalStatusField — so the status mirror is never blocked')", - "packages/plugins/plugin-approvals/src/status-mirror-cascade.integration.test.ts (the cascade-identity pin: real kernel + real SQL store, positive decider-identity case and the load-bearing user-less negative)" + "packages/spec/src/automation/approval.zod.ts#approvalStatusField (approvalStatusField — 'Should be readonly on the object. Omitted ⇒ status is exposed only via sys_approval_request'; the docblock's four-value list, which the service's fifth value 'returned' outgrew)", + "packages/plugins/plugin-approvals/src/approval-service.ts#mirrorStatusField (mirrorStatusField — elevated but not anonymous #3783: system write carrying the actor's userId; actorId null only for machine transitions; warn-not-throw on failure) + the six call sites (dead-run sweep, deliberately user-less)", + "packages/plugins/plugin-approvals/src/lifecycle-hooks.ts#mirror (the record lock's whitelist: 'block, EXCEPT when the only changed field is the configured approvalStatusField — so the status mirror is never blocked')", + "packages/plugins/plugin-approvals/src/status-mirror-cascade.integration.test.ts#kernel (the cascade-identity pin: real kernel + real SQL store, positive decider-identity case and the load-bearing user-less negative)" ], "history": [ { "revision": 1, "date": "2026-08-30", "change": "initial — the opt-in status mirror had no coverage. Grounded in the six service write sites (including 'returned', which the spec docblock omits — recorded so a run asserts actual behavior), the lock hook's mirror-field-only whitelist, and the #3783 acting-user attribution the existing integration test pins; no showcase flow declares the key, so the fixture rides the scratch-flow route (knownGaps)", "ref": "#sweep-2026-08-30" } @@ -1409,7 +1409,7 @@ "traps": ["automation-input", "hydration-race"], "source": [ "objectui apps/console/src/pages/system/ApprovalsInboxPage.tsx (the list keyboard effect — j/k/arrows/Enter/x/space/a/r; ownership guards: selectedId/dialog yield, INPUT/TEXTAREA/SELECT/contentEditable, [role=alertdialog]), (drawer ←/→ walk), (quickDecidable = isActionable AND no declared decision outputs, #2829), (inlineApprove/inlineReject → approvalsApi.approve/reject — the same routes as the buttons), (the focus ring classes), (the hint bar copy), (the file's own keyboard contract docblock)", - "examples/app-showcase/src/automation/flows/dynamic-approval.flow.ts (the stock decisionOutputs row the gating contrast rides)" + "examples/app-showcase/src/automation/flows/dynamic-approval.flow.ts#decisionOutputs (the stock decisionOutputs row the gating contrast rides)" ], "history": [ { "revision": 1, "date": "2026-08-30", "change": "initial — the inbox's keyboard surface (documented in the page's own header and advertised by its hint bar) had no checklist coverage; the load-bearing halves are that r/a land the same server transition as the buttons and that quick-decidability gates hotkeys off rows whose decisions require typed outputs", "ref": "#sweep-2026-08-30" } diff --git a/docs/qa/platform-checklist/areas/attachments-storage.json b/docs/qa/platform-checklist/areas/attachments-storage.json index 9e2e56f058..e8aff08fb8 100644 --- a/docs/qa/platform-checklist/areas/attachments-storage.json +++ b/docs/qa/platform-checklist/areas/attachments-storage.json @@ -20,14 +20,14 @@ "call": "POST /api/v1/packages", "body": { "manifest": { "id": "com.objectstack.qa.attachments", "name": "QA attachments fixture", "version": "1.0.0", "type": "app" }, "enableOnInstall": true }, "expect": "2xx with the installed package echoed back. A re-run against a live DB 409s on the duplicate id — send `overwrite: true` deliberately rather than adding it by reflex (the guard exists so a second install never silently clobbers the first).", - "source": "POST /api/v1/packages is the dispatcher install route (packages/rest/src/rest-route-ledger.ts note; client `packages.install`); the body shape { manifest, enableOnInstall?, overwrite? } is URL- and body-pinned in packages/client/src/client.test.ts" + "source": "POST /api/v1/packages is the dispatcher install route (packages/rest/src/rest-route-ledger.ts#packages note; client `packages.install`); the body shape { manifest, enableOnInstall?, overwrite? } is URL- and body-pinned in packages/client/src/client.test.ts" }, { "step": 2, "call": "PUT /api/v1/meta/object/qa_vault?package=com.objectstack.qa.attachments", "body": { "name": "qa_vault", "label": "QA Vault", "sharingModel": "private", "enable": { "files": true }, "fields": { "name": { "type": "text", "label": "Name", "required": true } } }, "expect": "2xx. This is THE object the area was missing: private OWD (owner-only) AND attachments-enabled, so a member who is not the owner genuinely cannot read the parent and the 403 deny side becomes provable.", - "source": "`sharingModel` is a TOP-LEVEL object key, enum ['private','public_read','public_read_write','controlled_by_parent'] (packages/spec/src/data/object.zod.ts; ADR-0090 D4 — legacy aliases removed). `enable.files` is the #2727 attachments opt-in, default false (object.zod.ts). `?package=` is read as `query.package` and threaded to saveMetaItem as packageId (packages/runtime/src/domains/meta.ts,319)" + "source": "`sharingModel` is a TOP-LEVEL object key, enum ['private','public_read','public_read_write','controlled_by_parent'] (packages/spec/src/data/object.zod.ts#legacy; ADR-0090 D4 — legacy aliases removed). `enable.files` is the #2727 attachments opt-in, default false (object.zod.ts). `?package=` is read as `query.package` and threaded to saveMetaItem as packageId (packages/runtime/src/domains/meta.ts,319)" }, { "step": 3, @@ -41,7 +41,7 @@ "call": "PUT /api/v1/meta/object/qa_nofiles?package=com.objectstack.qa.attachments", "body": { "name": "qa_nofiles", "label": "QA No Files", "sharingModel": "public_read", "fields": { "name": { "type": "text", "label": "Name", "required": true } } }, "expect": "2xx. `enable.files` is OMITTED deliberately (it defaults to false) — this is the FILES_DISABLED probe target, purpose-built rather than borrowed from showcase_account, so the negative keeps holding if showcase ever enables files on its own objects.", - "source": "packages/spec/src/data/object.zod.ts — enable.files defaults to false; 'Opt-in: true surfaces the panel and permits attachments to target this object; otherwise any write that makes an attachment target it is rejected (403 FILES_DISABLED) — a create and an update that re-points an existing attachment alike' (re-quoted post-#10733; the prior citation ended at 'creation is rejected', which #10170 made stale — see attach-requires-parent-edit clause 5)" + "source": "packages/spec/src/data/object.zod.ts#create — enable.files defaults to false; 'Opt-in: true surfaces the panel and permits attachments to target this object; otherwise any write that makes an attachment target it is rejected (403 FILES_DISABLED) — a create and an update that re-points an existing attachment alike' (re-quoted post-#10733; the prior citation ended at 'creation is rejected', which #10170 made stale — see attach-requires-parent-edit clause 5)" }, { "step": 5, @@ -84,7 +84,7 @@ "call": "PUT /api/v1/meta/object/qa_media?package=com.objectstack.qa.media", "body": { "name": "qa_media", "label": "QA Media", "sharingModel": "public_read_write", "fields": { "name": { "type": "text", "label": "Name", "required": true }, "poster": { "type": "image", "label": "Poster", "accept": ["image/png", "image/jpeg"], "maxSize": 1048576 }, "doc": { "type": "file", "label": "Doc", "accept": [".pdf"] } } }, "expect": "2xx. `poster` is the MIME-entry + maxSize probe; `doc` (accept = ['.pdf'] ONLY, no maxSize) is the extension-entry probe whose dotless-filename hole is a documented boundary of the enforcement. `enable.files` is deliberately ABSENT: field-owned files ride file-reference-lifecycle.ts (activeFileFields keys on file-class field types), not the #2727 sys_attachment opt-in gate, so the object needs no attachments enablement.", - "source": "accept/maxSize are declared FieldSchema keys since ADR-0104 D3 wave 2 (packages/spec/src/data/field.zod.ts — 'Offered to the file picker AND enforced on write'); authoring-call shape identical to qa-scratch-authz step 2 (meta.ts,319 for ?package=); file-class field set is FILE_REFERENCE_TYPES = image/file/avatar/video/audio (packages/spec/src/data/field-value.zod.ts)" + "source": "accept/maxSize are declared FieldSchema keys since ADR-0104 D3 wave 2 (packages/spec/src/data/field.zod.ts#shape — 'Offered to the file picker AND enforced on write'); authoring-call shape identical to qa-scratch-authz step 2 (meta.ts,319 for ?package=); file-class field set is FILE_REFERENCE_TYPES = image/file/avatar/video/audio (packages/spec/src/data/field-value.zod.ts)" } ], "teardown": "DELETE /api/v1/packages/com.objectstack.qa.media — or discard the isolated file DB, the cheaper path an isolated boot makes free.", @@ -160,7 +160,7 @@ "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, "source": [ "docs/plans/release-15.1-test-plan.md §A12 / §C1", - "packages/services/service-storage/src/storage-routes.ts (upload session gate #2755, envelope #3689, 302 door)", + "packages/services/service-storage/src/storage-routes.ts#session (upload session gate #2755, envelope #3689, 302 door)", "packages/services/service-storage/src/storage-route-ledger.ts (the audited route set at the default base; _local/raw is a server-only capability URL)" ], "history": [ @@ -240,8 +240,8 @@ "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts (the CLOSED side — clauses 0, 1 and 2: anonymous 401 AUTH_REQUIRED on both download routes, 403 ATTACHMENT_DOWNLOAD_DENIED for an authenticated member who cannot read the parent, and the uploader/entitled 200 in the {success, data:{url}} envelope) + packages/qa/dogfood/test/attachments-public-read-acl.dogfood.test.ts (clause 3, the OPEN side, asserted on ONE attachments-scope file attached to a private parent: the anonymous capability URL is 401 BEFORE the flip, 302 AFTER acl='public_read' — and the redirect is FOLLOWED anonymously and the served bytes compared, because a 302 into a wall would satisfy a status-only assertion — the /url sibling likewise reopens in its declared envelope, and flipping the acl back to 'private' re-closes both routes, which is what shows the acl VALUE is the cause rather than state accrued by the earlier requests. A sibling file on the SAME parent with the same uploader and scope keeps its 401/403, so the opt-out is per FILE and not a global fail-open. The TTL is read back out of the minted capability URL to prove WHICH branch ran: public_read returns presignedTtl (measured >3000s) while an authorized gated grant returns downloadTtl (measured <=300s) — a distinction no status code exposes, so a widened gate admitting the anonymous caller through the AUTHORIZED path would still be caught. The field-owned gating class (ref_object/ref_id, ADR-0104 D3 wave 2) is covered on both sides too, including its distinct FILE_DOWNLOAD_DENIED deny code, which is the evidence that the field-owned arm — not the attachments arm — is the one that ran). STILL MANUAL: clause 4, the browser's friendly denial copy in objectui's RecordAttachmentsPanel, is a screenshot oracle over a console bundle this repo does not build. It has NO pin, must be hand-driven, and the clause-3 pin must NOT be read as covering it." }, "source": [ "docs/plans/release-15.1-test-plan.md §A12 / §C1 (#2755/#2970)", - "packages/services/service-storage/src/storage-routes.ts (authorizeDownload: gating classes, verdict→status mapping, downloadTtl vs presignedTtl)", - "packages/services/service-storage/src/storage-service-plugin.ts (buildFileReadAuthorizer: owner bypass, field-owned single-parent read, fail-closed delegate)", + "packages/services/service-storage/src/storage-routes.ts#authorizeDownload (authorizeDownload: gating classes, verdict→status mapping, downloadTtl vs presignedTtl)", + "packages/services/service-storage/src/storage-service-plugin.ts#buildFileReadAuthorizer (buildFileReadAuthorizer: owner bypass, field-owned single-parent read, fail-closed delegate)", "objectui packages/app-shell/src/views/RecordAttachmentsPanel.tsx (friendly denial copy, objectui#2532)" ], "history": [ @@ -321,7 +321,7 @@ "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts (clauses 0 and 2: the restricted member's scoped list is empty for an invisible parent while the entitled one is not, and a by-id read of the hidden row is 403/404) + packages/qa/dogfood/test/attachments-parent-rls-count-parity.dogfood.test.ts (clause 1, the COUNT: the restricted member's list `total` excludes invisible-parent rows while the entitled caller's includes them on the same request, engine.count() under the member's OWN resolved authz context (resolveAuthzContext, not a hand-built principal) is filtered identically, a count NARROWED to the invisible parent answers 0 for the member and the true number for admin, and paging the member all the way through yields exactly the reported total — so rows-vs-count parity is asserted as one fact rather than two numbers that happen to agree. ⚠ THE TRAP, and why every assertion passes $top: protocol.findData only calls engine.count() when the request carries a PAGE LIMIT; without one it sets total = records.length, which is trivially self-consistent and stays green with count() fully unfiltered because the count path never runs. Each assertion therefore also requires total > records.length, proving the count call happened rather than assuming it) + packages/qa/dogfood/test/attachments-parent-rls-scan-cap.dogfood.test.ts (clause 3, the READ_SCAN_LIMIT half: with >2000 candidate join rows the broad read's pre-scan is truncated, and the pin asserts the '[storage] attachment read visibility ... 2000-row cap ... fail-closed' warning fires (the clause's own log oracle — silence plus leaked rows is the failure), that visible rows are DROPPED rather than the un-scanned remainder being admitted, that the invisible-parent row never appears, that every row returned has a genuinely resolvable visible parent, and that a row the broad read omitted is still returned by a SCOPED read that does not hit the cap — the contrast without which 'missing' would be equally explained by 'invisible anyway'. The seed puts visible rows at BOTH ENDS of the insertion order with the filler bulk between, because the pre-scan has no ORDER BY and the driver was measured NOT to return insertion order; the control asserts the window is full and some visible parent is outside it rather than trusting either). ALSO PINNED, as the reachable half of clause 3's fail-compute arm: a row whose parent RECORD no longer resolves (a dangling parent_id, what a hard-deleted parent leaves behind) is excluded from both rows and count. NOT REACHED, and not to be read as covered: (a) the unknown parent_OBJECT arm (`catch { visible = [] }`) has no reachable fixture from this lane — the #2727 enable.files gate refuses to create a sys_attachment row against a non-files object even under system context (measured: \"File attachments are not enabled for object 'att_ghost_object'\"), so such a row cannot be seeded; (b) the middleware-level outer catch ('filter failed, denying all') is not provokable through the API, since a where malformed enough to break the system pre-scan also fails the caller's own read and surfaces as a 4xx instead." }, "source": [ "docs/plans/release-15.1-test-plan.md §C3 (#2970)", - "packages/services/service-storage/src/attachment-access-hooks.ts (installAttachmentReadVisibility: middleware over find/findOne/count/aggregate, deny-all sentinel, READ_SCAN_LIMIT fail-closed cap)", + "packages/services/service-storage/src/attachment-access-hooks.ts#installAttachmentReadVisibility (installAttachmentReadVisibility: middleware over find/findOne/count/aggregate, deny-all sentinel, READ_SCAN_LIMIT fail-closed cap)", "packages/services/service-storage/src/attachment-read-visibility.test.ts" ], "history": [ @@ -416,11 +416,11 @@ "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts (clauses 0, 1, 2 and 4: attach without parent EDIT is 403 ATTACHMENT_PARENT_ACCESS while the same member's LIST still succeeds, uploaded_by is server-stamped over a spoofed value, delete splits uploader-vs-outsider, and FILES_DISABLED) + packages/qa/dogfood/test/attachments-unscoped-delete-gate.dogfood.test.ts, which now carries TWO blocks and pins CLAUSE 3 end to end. Block 1 (split fixture — the caller owns one row and may not touch the other): the predicate-less `{ multi: true }` delete is refused with ATTACHMENT_DELETE_DENIED / 403 / the 'Refusing an unscoped multi-delete of attachments' message and removes NOTHING, not even the row it WAS entitled to, so the refusal is authoritative and cannot land as a partial delete; a match-all `where: {}` reaches the same verdict by a DIFFERENT rule and asserts the PER-ROW message instead, which is what keeps the #9797 boundary honest (the whole-operation dispatch is deliberately scoped to a delete with no `where` at all, so `where: {}` stays a real query); and a predicate that MATCHES NOTHING is allowed through, the live half of the #4757 reasoning. Block 2 (the discriminating fixture — the caller uploaded EVERY row, so the per-row gate has nothing to refuse): the predicate-less delete is still refused, which is the only shape that proves 'refused OUTRIGHT' rather than 'every row happened to be denied'; the refusal also fires on an EMPTY table (the zero-match limb — the per-row dispatch is gated on matched rows, so a handler-only fix could never reach it); and both sides are held by the same caller sweeping the same rows with a SCOPED predicate successfully, plus a scoped `where: {}` over the empty table resolving. In both blocks the member is granted the sys_attachment delete bit first and the grant is asserted, because otherwise RBAC refuses ahead of the attachment hook and the file would be green for a reason it is not about. ⭐ Block 2 exists because block 1 provably cannot see this clause: ablating `dispatchUnscopedMultiWrite` from both registrations (rebuilt, dist-preflighted) leaves block 1 5/5 GREEN, while block 2 goes red BY RESOLVING — 'promise resolved \"2\"', which is #9719's measured wipe reproduced. Unit-level companion, also through a wired engine rather than a hand-built context: packages/services/service-storage/src/attachment-access-hooks.test.ts, describe 'unscoped multi-delete (no id, no where) — #4757 through the wired engine (#9719)'. CLAUSE 5 (the UPDATE-verb FILES_DISABLED re-point, new) has a HANDLER/ENGINE-level pin, NOT the wired dogfood/REST lane above: packages/plugins/plugin-audit/src/capability-gate-update-verb.test.ts, describe '[#10170] enable.files is asked on the UPDATE verb too' — runs a real ObjectQL (stub driver) rather than the hand-rolled fake in audit-writers.test.ts (whose registerHook ignores the { object } scope, so it cannot observe a registration-scope regression), and pins BOTH dispatch shapes (by-id AND predicate/per-row) refusing 403 FILES_DISABLED on re-point while leaving the row's parent_object unchanged, plus the positive (a re-point onto a files-ENABLED parent still succeeds) and the negative-control (an update that never names parent_object is not re-checked at all). This engine-level pin is why the checklist step above can isolate the refusal to FILES_DISABLED with confidence; it is NOT itself evidence about the wired REST PATCH lane, which is what clause 5's own oracle drives by hand." }, "source": [ "docs/plans/release-15.1-test-plan.md §A12 (attach 需 parent EDIT)", - "packages/services/service-storage/src/attachment-access-hooks.ts (beforeInsert canEdit gate + uploaded_by stamping; beforeDelete/beforeUpdate uploader-or-editor + #4757 unscoped refusal reached via the `dispatchUnscopedMultiWrite` declaration (#9719/#9974), MULTI_WRITE_AUTH_LIMIT fail-closed; authorizes an update-verb re-point onto the NEW parent, the access half clause 5 relies on)", - "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts (items 3, a, f; FILES_DISABLED)", - "packages/plugins/plugin-audit/src/audit-writers.ts (enforceFilesCapability — the #2727 enable.files gate; [#10170] registered it on beforeUpdate as well as beforeInsert because the flag gates the TARGET object a write makes an attachment point at, not the create/update verb) — the FILES_DISABLED source for both clause 4 (create) and the new clause 5 (update/re-point)", - "packages/spec/src/data/object.zod.ts (enable.files describe string, post-#10733: 'otherwise any write that makes an attachment target it is rejected (403 FILES_DISABLED) — a create and an update that re-points an existing attachment alike')", - "packages/plugins/plugin-audit/src/capability-gate-update-verb.test.ts (the #10170 engine-level pin for the update-verb capability gate — both files and feeds, both dispatch shapes)" + "packages/services/service-storage/src/attachment-access-hooks.ts#canEdit (beforeInsert canEdit gate + uploaded_by stamping; beforeDelete/beforeUpdate uploader-or-editor + #4757 unscoped refusal reached via the `dispatchUnscopedMultiWrite` declaration (#9719/#9974), MULTI_WRITE_AUTH_LIMIT fail-closed; authorizes an update-verb re-point onto the NEW parent, the access half clause 5 relies on)", + "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts#FILES_DISABLED (items 3, a, f; FILES_DISABLED)", + "packages/plugins/plugin-audit/src/audit-writers.ts#enforceFilesCapability (enforceFilesCapability — the #2727 enable.files gate; [#10170] registered it on beforeUpdate as well as beforeInsert because the flag gates the TARGET object a write makes an attachment point at, not the create/update verb) — the FILES_DISABLED source for both clause 4 (create) and the new clause 5 (update/re-point)", + "packages/spec/src/data/object.zod.ts#create (enable.files describe string, post-#10733: 'otherwise any write that makes an attachment target it is rejected (403 FILES_DISABLED) — a create and an update that re-points an existing attachment alike')", + "packages/plugins/plugin-audit/src/capability-gate-update-verb.test.ts#engine (the #10170 engine-level pin for the update-verb capability gate — both files and feeds, both dispatch shapes)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item from the access-hook source: EDIT-not-read attach gate, server stamping, delete authorization matrix and the #4757 unscoped-delete refusal, each with its exact deny code", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -590,8 +590,8 @@ "traps": ["stale-dist"], "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, "source": [ - "packages/services/service-storage/src/attachment-lifecycle.ts (createSysFileReapGuard — the verdict variants are its documented branch set, incl. the ADR-0104 isCollectionOpen gate re-read each sweep)", - "packages/services/service-storage/src/objects/system-file.object.ts (lifecycle: ttl deleted_at+30d, retention 7d onlyWhen pending)", + "packages/services/service-storage/src/attachment-lifecycle.ts#createSysFileReapGuard (createSysFileReapGuard — the verdict variants are its documented branch set, incl. the ADR-0104 isCollectionOpen gate re-read each sweep)", + "packages/services/service-storage/src/objects/system-file.object.ts#deleted_at (lifecycle: ttl deleted_at+30d, retention 7d onlyWhen pending)", "docs/plans/release-15.1-test-plan.md §C4 (#2755/#2970)" ], "history": [ @@ -674,12 +674,12 @@ "traps": ["stale-dist"], "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/attachments-permission-matrix.dogfood.test.ts" }, "source": [ - "packages/services/service-storage/src/attachment-lifecycle.ts (createUploadSessionReapGuard — abort-before-reap contract, completed/no-backend confirm branches, veto-on-failure)", - "packages/services/service-storage/src/objects/system-upload-session.object.ts (status enum = the variants list; ttl expires_at+1d, retention 7d terminal statuses)", + "packages/services/service-storage/src/attachment-lifecycle.ts#createUploadSessionReapGuard (createUploadSessionReapGuard — abort-before-reap contract, completed/no-backend confirm branches, veto-on-failure)", + "packages/services/service-storage/src/objects/system-upload-session.object.ts#expires_at (status enum = the variants list; ttl expires_at+1d, retention 7d terminal statuses)", "packages/services/service-storage/src/storage-route-ledger.ts (upload-chunked family)", - "packages/services/service-storage/src/storage-routes.ts (POST /upload/chunked init contract: filename/mimeType/totalSize required, else 400 INVALID_REQUEST; chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880), i.e. a 5 MiB floor)", - "packages/services/service-storage/src/storage-routes.ts (expireIfPastDeadline / markSessionFailed — the #7667 producers for 'expired' and 'failed'; chunk + complete refuse 410 UPLOAD_SESSION_EXPIRED, progress reports the status)", - "packages/spec/src/api/storage.zod.ts (UploadProgressSchema.status — the client-facing declaration the enum must stay in step with)", + "packages/services/service-storage/src/storage-routes.ts#mimeType (POST /upload/chunked init contract: filename/mimeType/totalSize required, else 400 INVALID_REQUEST; chunkSize = Math.max(reqChunkSize ?? 5242880, 5242880), i.e. a 5 MiB floor)", + "packages/services/service-storage/src/storage-routes.ts#expireIfPastDeadline (expireIfPastDeadline / markSessionFailed — the #7667 producers for 'expired' and 'failed'; chunk + complete refuse 410 UPLOAD_SESSION_EXPIRED, progress reports the status)", + "packages/spec/src/api/storage.zod.ts#UploadProgressSchema (UploadProgressSchema.status — the client-facing declaration the enum must stay in step with)", "docs/plans/release-15.1-test-plan.md §C4 (#2970 item 4)" ], "history": [ @@ -754,9 +754,9 @@ "source": [ "docs/plans/release-15.1-test-plan.md §C2 (#3051 + objectui#2585)", "objectui e2e/live/grid-file-upload.spec.ts (the live pin: auto-column, input-not-text, chip, batch payload shape)", - "examples/app-showcase/src/data/objects/invoice.object.ts (showcase_invoice_line.receipt = Field.file, objectui#2360 note)", + "examples/app-showcase/src/data/objects/invoice.object.ts#showcase_invoice_line (showcase_invoice_line.receipt = Field.file, objectui#2360 note)", "docs/adr/0104-field-runtime-value-shape-contract.md — D3 'File-as-reference: field values point into sys_file': the STORED form is an opaque fileId string (the inline {url, name, size} blob is retired from the write path); the {id, name, size, mimeType, url} FileValueSchema is the EXPANDED form produced at read time, whose url is 'derived, never stored'; field-referenced files get parent-derived read checks", - "packages/services/service-storage/src/storage-routes.ts — /upload/complete returns fileId, documented as 'the opaque sys_file id — the value a file field stores as a reference (ADR-0104 D3)'", + "packages/services/service-storage/src/storage-routes.ts#fileId — /upload/complete returns fileId, documented as 'the opaque sys_file id — the value a file field stores as a reference (ADR-0104 D3)'", "ADR-0104 D3's parent-derived read gating for field-owned files is exercised by attachments-storage.download-authz-both-sides (which carries the non-entitled personas). Cross-reference, do not duplicate", "ADR-0059 — the FORM-side Confirm-disabled-while-uploading guard is records-forms.upload-guard-blocks-confirm; this item owns the storage/persistence side. Cross-reference, do not duplicate" ], @@ -862,10 +862,10 @@ "traps": ["absence-inference", "wrong-panel"], "automated": { "kind": "unit", "ref": "packages/services/service-storage/src/file-reference-lifecycle.test.ts ('accept / maxSize enforcement' describe) — HANDLER-DIRECT: drives the hook through a fake engine, covering the semantics of clauses 0/1/4/5/6 (rejection, per-entry vocabulary, missing-metadata pass, no-constraints pass) at the unit level. It is NOT evidence about the wired REST lane or the wire envelope (clause 2), and per the attach-requires-parent-edit clause-3 lesson a handler-direct green can coexist with different wired behaviour — the dogfood lane has NO pin for this seam, which is precisely why this item exists." }, "source": [ - "packages/services/service-storage/src/file-reference-lifecycle.ts (the rationale: the widget check is a convenience, the server re-check is the control; missing metadata is not evidence of a violation), (assertFileConstraints — maxSize guard, testable filter + early return, accept mismatch throw), (matchesAcceptEntry — exact MIME / type\\/* wildcard /.ext-against-NAME vocabulary), (the check rides copy-on-claim in the before hooks — beforeInsert, beforeUpdate — and unknown ids are skipped)", - "packages/spec/src/data/field.zod.ts (accept/maxSize declared on FieldSchema, 'Offered to the file picker AND enforced on write') + packages/spec/liveness/field.json (both keys `live` with this enforcement as evidence)", - "packages/services/service-storage/src/storage-routes.ts (presigned) and (chunked) — mime_type/size persisted VERBATIM from the client body: the declaration-based boundary in knownGaps", - "packages/spec/src/api/error-code-ledger.zod.ts (ERR_FILE_CONSTRAINT registered) vs file-reference-lifecycle.ts (no status declared) vs packages/rest/src/error-response.ts classifyDataError (no branch, declared-status passthrough skipped, UNCLASSIFIED_FAULT terminal) — the clause-2 wire analysis; file-reference-lifecycle.ts (FileFieldBulkWriteError's `status: 400` note is the in-module precedent for what a 4xx exit requires)", + "packages/services/service-storage/src/file-reference-lifecycle.ts#assertFileConstraints (the rationale: the widget check is a convenience, the server re-check is the control; missing metadata is not evidence of a violation), (assertFileConstraints — maxSize guard, testable filter + early return, accept mismatch throw), (matchesAcceptEntry — exact MIME / type\\/* wildcard /.ext-against-NAME vocabulary), (the check rides copy-on-claim in the before hooks — beforeInsert, beforeUpdate — and unknown ids are skipped)", + "packages/spec/src/data/field.zod.ts#maxSize (accept/maxSize declared on FieldSchema, 'Offered to the file picker AND enforced on write') + packages/spec/liveness/field.json#live (both keys `live` with this enforcement as evidence)", + "packages/services/service-storage/src/storage-routes.ts#mime_type (presigned) and (chunked) — mime_type/size persisted VERBATIM from the client body: the declaration-based boundary in knownGaps", + "packages/spec/src/api/error-code-ledger.zod.ts#status (ERR_FILE_CONSTRAINT registered) vs file-reference-lifecycle.ts (no status declared) vs packages/rest/src/error-response.ts#classifyDataError classifyDataError (no branch, declared-status passthrough skipped, UNCLASSIFIED_FAULT terminal) — the clause-2 wire analysis; file-reference-lifecycle.ts (FileFieldBulkWriteError's `status: 400` note is the in-module precedent for what a 4xx exit requires)", "packages/services/service-storage/CHANGELOG.md 17.0.0-rc.0 (changeset fe67e34, ADR-0104 D3 wave 2 PR-5a) — grounds since: v17", "records-forms.upload-guard-blocks-confirm owns the CLIENT half (ADR-0059 dialog guard over ACTION-PARAM accept/maxSize, examples/app-showcase/src/ui/actions/index.ts,337). Cross-reference, do not duplicate — that item never touches this record-write seam" ], diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index 1e6af0be9c..ca13871a9c 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -23,7 +23,7 @@ "enableOnInstall": true }, "expect": "2xx with the installed package echoed back. The id is deliberately distinct from the other QA recipes so this one installs and tears down independently. A re-run against a live DB 409s on the duplicate id — send `overwrite: true` deliberately, never by reflex.", - "source": "POST /api/v1/packages is the dispatcher install route (packages/rest/src/rest-route-ledger.ts note; client `packages.install`); body shape { manifest, enableOnInstall?, overwrite? } pinned in packages/client/src/client.test.ts" + "source": "POST /api/v1/packages is the dispatcher install route (packages/rest/src/rest-route-ledger.ts#packages note; client `packages.install`); body shape { manifest, enableOnInstall?, overwrite? } pinned in packages/client/src/client.test.ts" }, { "step": 2, @@ -54,13 +54,13 @@ "edges": [{ "id": "e1", "source": "start", "target": "boom" }] }, "expect": "2xx on the author. On trigger the run DISPATCHES, the http node fails against the unroutable host, and — with no try_catch region and no errorHandling.strategy — the run ends `status: 'failed'`, which is the 400 FLOW_FAILED row. ⚠️ Do NOT wrap it in try_catch: a handled failure is a SUCCESSFUL run and scores the wrong row. The unroutable-host technique (api.example.com) is the one automation.flow-error-handling already relies on for a deterministic failure.", - "source": "the 400 row keys on the engine's lifecycle verdict `status: 'failed'`, never on message text (packages/runtime/src/flow-dispatch-status.ts, classifyFlowRefusal); examples/app-showcase/src/automation/flows/index.ts (showcase_resilient_sync) is the precedent for the unroutable host" + "source": "the 400 row keys on the engine's lifecycle verdict `status: 'failed'`, never on message text (packages/runtime/src/flow-dispatch-status.ts#classifyFlowRefusal, classifyFlowRefusal); examples/app-showcase/src/automation/flows/index.ts#showcase_resilient_sync (showcase_resilient_sync) is the precedent for the unroutable host" }, { "step": 4, "call": "toggle a stock record-change flow OFF for the 409 row: POST /api/v1/automation/showcase_urgent_task_alert/toggle {\"enabled\": false}", "expect": "2xx. No authoring needed — the 409 FLOW_DISABLED row is reachable on stock fixtures. ⚠️ Toggle it back ON in teardown: it is the fixture automation.flow-toggle-kill-switch drives, and leaving it off silently changes that item's baseline.", - "source": "packages/runtime/src/domains/automation.ts (POST /:name/toggle → engine.toggleFlow); automation.flow-toggle-kill-switch names showcase_urgent_task_alert as the only type:'record_change' flow" + "source": "packages/runtime/src/domains/automation.ts#flow (POST /:name/toggle → engine.toggleFlow); automation.flow-toggle-kill-switch names showcase_urgent_task_alert as the only type:'record_change' flow" } ], "teardown": "POST /api/v1/automation/showcase_urgent_task_alert/toggle {\"enabled\": true} to undo step 4, then DELETE /api/v1/packages/com.objectstack.qa.automation (client `packages.uninstall`, ledgered at rest-route-ledger.ts) — or discard the isolated file DB, the cheaper path an isolated boot makes free. The toggle is NOT covered by discarding the DB if you reused a boot, so undo it explicitly.", @@ -138,9 +138,9 @@ ], "source": [ "#3358 §2 — 'the developer Flow Runs page renders steps flat; looking there alone reads as a miss'", - "packages/spec/src/automation/execution.zod.ts (ExecutionStepLogSchema parentNodeId/iteration/regionKind)", + "packages/spec/src/automation/execution.zod.ts#ExecutionStepLogSchema (ExecutionStepLogSchema parentNodeId/iteration/regionKind)", "objectui packages/app-shell/src/views/metadata-admin/previews/FlowRunsPanel.tsx (#1505 buildStepTree)", - "examples/app-showcase/src/automation/flows/index.ts (BatchRemindersFlow)" + "examples/app-showcase/src/automation/flows/index.ts#BatchRemindersFlow (BatchRemindersFlow)" ], "history": [ { @@ -231,7 +231,7 @@ "source": [ "#3358 §2", "packages/spec/src/automation/time-relative-trigger.zod.ts", - "examples/app-showcase/src/automation/flows/index.ts (TaskDueReminderFlow, #1874)", + "examples/app-showcase/src/automation/flows/index.ts#TaskDueReminderFlow (TaskDueReminderFlow, #1874)", "packages/triggers/trigger-schedule/src/time-relative-trigger.ts" ], "history": [ @@ -317,7 +317,7 @@ "source": [ "#3358 §2 (recompute half proven; editor half explicitly left unticked)", "examples/app-showcase/src/data/objects/expense-report.object.ts (the six roll-up shapes and their filters)", - "packages/runtime/src/domains/packages.ts (POST /packages/:id/duplicate — ADR-0070 D4 'duplicate base', the writable-target route)", + "packages/runtime/src/domains/packages.ts#packages (POST /packages/:id/duplicate — ADR-0070 D4 'duplicate base', the writable-target route)", "docs/adr/0070-package-first-authoring.md §D4 ('clone a base into a new writable package')" ], "history": [ @@ -447,14 +447,14 @@ "ref": "packages/qa/dogfood/test/flow-node.dogfood.test.ts" }, "source": [ - "packages/spec/src/automation/flow.zod.ts (FlowNodeAction — the built-in seed set; type validated at registerFlow, not by a closed enum)", - "packages/spec/src/automation/control-flow.zod.ts (LOOP_NODE_TYPE / PARALLEL_NODE_TYPE / TRY_CATCH_NODE_TYPE, ADR-0031)", - "packages/spec/src/automation/approval.zod.ts (APPROVAL_NODE_TYPE, APPROVAL_REVISE_NODE_TYPE)", - "examples/app-showcase/src/coverage.ts (flowNodeTypes + FLOW_NODE_WAIVERS)", + "packages/spec/src/automation/flow.zod.ts#FlowNodeAction (FlowNodeAction — the built-in seed set; type validated at registerFlow, not by a closed enum)", + "packages/spec/src/automation/control-flow.zod.ts#LOOP_NODE_TYPE (LOOP_NODE_TYPE / PARALLEL_NODE_TYPE / TRY_CATCH_NODE_TYPE, ADR-0031)", + "packages/spec/src/automation/approval.zod.ts#APPROVAL_NODE_TYPE (APPROVAL_NODE_TYPE, APPROVAL_REVISE_NODE_TYPE)", + "examples/app-showcase/src/coverage.ts#flowNodeTypes (flowNodeTypes + FLOW_NODE_WAIVERS)", "examples/app-showcase/src/automation/flows/index.ts", "packages/runtime/src/route-ledger.ts (GET /automation/actions, POST /automation/:name/trigger, GET /automation/:name/runs)", "docs/adr/0018-unified-node-action-registry.md §M1 (registerFlow() soft-validates node types against the live registry — 'warn, don't hard-fail')", - "packages/services/service-automation/src/engine.ts (registerFlow's ADR-0018 §M1 comment + the post-seal warn branch; warnUnknownNodeTypes's message and its unknownTypes/knownTypes meta; the NO_EXECUTOR step error in executeNode)", + "packages/services/service-automation/src/engine.ts#registerFlow (registerFlow's ADR-0018 §M1 comment + the post-seal warn branch; warnUnknownNodeTypes's message and its unknownTypes/knownTypes meta; the NO_EXECUTOR step error in executeNode)", "#7545 (maintainer ruling 2026-08-11 — status quo upheld: soft-fail at registration is the intended posture, so 200-on-unknown-type is pass-by-design)" ], "history": [ @@ -570,11 +570,11 @@ "ref": "packages/qa/dogfood/test/flow-trigger-conformance.test.ts" }, "source": [ - "packages/spec/src/automation/flow.zod.ts (Flow.type enum: autolaunched|record_change|schedule|screen|api)", + "packages/spec/src/automation/flow.zod.ts#Flow (Flow.type enum: autolaunched|record_change|schedule|screen|api)", "packages/qa/dogfood/test/flow-trigger-conformance.ledger.ts (one enforced row per type, each with its runtime + proof)", - "packages/triggers/trigger-api/src/plugin.ts (HOOKS_PATH /api/v1/automation/hooks/:flowName/:hookId)", - "examples/app-showcase/src/automation/flows/index.ts (UrgentTaskAlertFlow #3427, ScheduledDigestFlow, InboundTaskWebhookFlow)", - "packages/spec/src/automation/execution.zod.ts (ExecutionLogSchema.trigger)" + "packages/triggers/trigger-api/src/plugin.ts#HOOKS_PATH (HOOKS_PATH /api/v1/automation/hooks/:flowName/:hookId)", + "examples/app-showcase/src/automation/flows/index.ts#UrgentTaskAlertFlow (UrgentTaskAlertFlow #3427, ScheduledDigestFlow, InboundTaskWebhookFlow)", + "packages/spec/src/automation/execution.zod.ts#ExecutionLogSchema (ExecutionLogSchema.trigger)" ], "history": [ { @@ -668,12 +668,12 @@ "single-datapoint" ], "source": [ - "examples/app-showcase/src/automation/flows/index.ts (ResilientSyncFlow, ADR-0031 try/catch/retry; canonical retry keys #4661)", - "packages/spec/src/automation/control-flow.zod.ts (TryCatchConfigSchema)", - "packages/spec/src/automation/execution.zod.ts (ExecutionStatus 'failed'; step status/error; regionKind; retryAttempt — declared since the schema was written, given its first producer by #7546)", - "packages/services/service-automation/src/engine.ts (runRegion's partialSteps sink — the failed attempt's tagged steps handed to the caller before the throw propagates; StepLogEntry.retryAttempt)", - "packages/services/service-automation/src/builtin/try-catch-node.ts (failed attempts accumulated across the retry ladder and folded into childSteps ahead of the surviving region's steps)", - "packages/services/service-automation/src/builtin/try-catch-failed-attempt-steps.test.ts (the #7546 unit pins, including 'a recovered container still reports success')", + "examples/app-showcase/src/automation/flows/index.ts#ResilientSyncFlow (ResilientSyncFlow, ADR-0031 try/catch/retry; canonical retry keys #4661)", + "packages/spec/src/automation/control-flow.zod.ts#TryCatchConfigSchema (TryCatchConfigSchema)", + "packages/spec/src/automation/execution.zod.ts#ExecutionStatus (ExecutionStatus 'failed'; step status/error; regionKind; retryAttempt — declared since the schema was written, given its first producer by #7546)", + "packages/services/service-automation/src/engine.ts#runRegion (runRegion's partialSteps sink — the failed attempt's tagged steps handed to the caller before the throw propagates; StepLogEntry.retryAttempt)", + "packages/services/service-automation/src/builtin/try-catch-node.ts#childSteps (failed attempts accumulated across the retry ladder and folded into childSteps ahead of the surviving region's steps)", + "packages/services/service-automation/src/builtin/try-catch-failed-attempt-steps.test.ts#container (the #7546 unit pins, including 'a recovered container still reports success')", "objectui packages/app-shell/src/views/metadata-admin/previews/FlowRunsPanel.tsx (run-level string error vs step-level {code,message})", "packages/runtime/src/route-ledger.ts (POST /automation — automation.create)", "#7546 (maintainer ruling 2026-08-11 — surface the failed try-region's steps; Option C's 'recovered' container status NOT adopted)" @@ -786,9 +786,9 @@ "source": [ "objectui e2e/live/screen-flow.spec.ts (framework#3528 — the trigger → dialog → resume → refresh seam)", "objectui packages/app-shell/src/views/FlowRunner.tsx (onClose = dismiss without POSTing resume; the durable suspension is untouched, so the paused run stays resumable)", - "examples/app-showcase/src/automation/flows/index.ts (ReassignWizardFlow)", + "examples/app-showcase/src/automation/flows/index.ts#ReassignWizardFlow (ReassignWizardFlow)", "packages/services/service-automation/src/builtin/screen-resume-validation.test.ts + src/screen-input-contract.ts (400 on missing required inputs)", - "packages/runtime/src/route-ledger.ts (resume + getScreen routes)" + "packages/runtime/src/route-ledger.ts#getScreen (resume + getScreen routes)" ], "history": [ { @@ -885,7 +885,7 @@ }, "source": [ "packages/qa/dogfood/test/flow-durable-suspend.dogfood.test.ts (#4470 · #4420 · #4518)", - "examples/app-showcase/src/automation/flows/index.ts (TaskFollowUpFlow, ProjectClosureFlow/ClosureSignoffSubflow — nested durable pause, linked-runs model)" + "examples/app-showcase/src/automation/flows/index.ts#TaskFollowUpFlow (TaskFollowUpFlow, ProjectClosureFlow/ClosureSignoffSubflow — nested durable pause, linked-runs model)" ], "history": [ { @@ -969,8 +969,8 @@ "single-datapoint" ], "source": [ - "examples/app-showcase/src/automation/flows/index.ts (TaskCompletedRestPingFlow, ShowcaseDeclarativeConnectorPingFlow, ShowcaseMcpConnectorEchoFlow)", - "examples/app-showcase/objectstack.config.ts (ConnectorRestPlugin/ConnectorSlackPlugin/ConnectorMcpPlugin + declarative connectors)", + "examples/app-showcase/src/automation/flows/index.ts#TaskCompletedRestPingFlow (TaskCompletedRestPingFlow, ShowcaseDeclarativeConnectorPingFlow, ShowcaseMcpConnectorEchoFlow)", + "examples/app-showcase/objectstack.config.ts#ConnectorRestPlugin (ConnectorRestPlugin/ConnectorSlackPlugin/ConnectorMcpPlugin + declarative connectors)", "packages/runtime/src/route-ledger.ts (GET /automation/connectors)", "ADR-0097 (provider-bound declarative connector instances)" ], @@ -1064,8 +1064,8 @@ "objectui apps/console/src/pages/developer/FlowRunsPage.tsx (FlowTestRunner + inline FlowRunsPanel; framework#3528 — hands a paused screen run to FlowRunner so it no longer orphans a paused row)", "objectui packages/app-shell/src/views/FlowRunner.tsx (the shared screen runner reused here)", "objectui apps/console/src/AppContent.tsx (Route path 'developer/flow-runs') + registerDeveloperComponents.tsx (ref 'developer:flow-runs')", - "packages/runtime/src/route-ledger.ts (POST /automation/:name/trigger, GET /automation/:name/runs, POST /automation/:name/runs/:runId/resume)", - "examples/app-showcase/src/automation/flows/index.ts (BatchRemindersFlow tasks input, ReassignWizardFlow screen)" + "packages/runtime/src/route-ledger.ts#runId (POST /automation/:name/trigger, GET /automation/:name/runs, POST /automation/:name/runs/:runId/resume)", + "examples/app-showcase/src/automation/flows/index.ts#BatchRemindersFlow (BatchRemindersFlow tasks input, ReassignWizardFlow screen)" ], "history": [ { @@ -1140,10 +1140,10 @@ "dispatcher-vs-hono-route" ], "source": [ - "packages/runtime/src/route-ledger.ts (POST /automation/:name/toggle → automation.toggle; GET /automation/_status → automation.getRuntimeStatus)", - "packages/services/service-automation/src/engine.ts (toggleFlow — writes the sys_metadata_activation row FIRST, then updates the flowLedgerDisabled projection and activateFlowTrigger/deactivateFlowTrigger; the process-local flowEnabled map this item originally cited is RETIRED by ADR-0126 §7.2) + (flowLedgerDisabled is a projection of the ledger, not the old map under a new name) + getFlowRuntimeStates enabled/bound", + "packages/runtime/src/route-ledger.ts#getRuntimeStatus (POST /automation/:name/toggle → automation.toggle; GET /automation/_status → automation.getRuntimeStatus)", + "packages/services/service-automation/src/engine.ts#toggleFlow (toggleFlow — writes the sys_metadata_activation row FIRST, then updates the flowLedgerDisabled projection and activateFlowTrigger/deactivateFlowTrigger; the process-local flowEnabled map this item originally cited is RETIRED by ADR-0126 §7.2) + (flowLedgerDisabled is a projection of the ledger, not the old map under a new name) + getFlowRuntimeStates enabled/bound", "docs/adr/0126-packaged-metadata-customization-model.md §7.2 (the durable ledger row replaces the process-local flowEnabled map as the sanctioned off-switch)", - "examples/app-showcase/src/automation/flows/index.ts (UrgentTaskAlertFlow — the record_change flow, #3427)", + "examples/app-showcase/src/automation/flows/index.ts#UrgentTaskAlertFlow (UrgentTaskAlertFlow — the record_change flow, #3427)", "automation.packaged-flow-disable-durable owns the halves this item deliberately does not: the durable row's shape, restart survival, and the ledger-message oracle; automation.packaged-flow-subflow-disable-refusal owns the §7.3 disable guard (showcase_urgent_task_alert has no packaged subflow callers, so this item's toggles are never refused by it)" ], "history": [ @@ -1265,14 +1265,14 @@ ], "traps": ["wrong-persona", "single-datapoint"], "source": [ - "packages/runtime/src/flow-dispatch-status.ts — the ONE definition of the trigger-side table (FlowRefusalCode, classifyFlowRefusal, isPausedRun, FLOW_NOT_FOUND_STATUS) and the note on why it is a module rather than a route mapper", + "packages/runtime/src/flow-dispatch-status.ts#FlowRefusalCode — the ONE definition of the trigger-side table (FlowRefusalCode, classifyFlowRefusal, isPausedRun, FLOW_NOT_FOUND_STATUS) and the note on why it is a module rather than a route mapper", "packages/runtime/src/domains/automation.ts — both trigger routes and the resume route's code→status arms", "packages/runtime/src/action-execution.ts and packages/runtime/src/endpoint-executor.ts — the other two doors reading the same table", - "packages/services/service-automation/src/engine.ts — the FLOW_NO_START_NODE exit; the disabled-flow exit stamps FLOW_DISABLED, and neither carries `status`, which is what lets a transport tell a never-dispatched exit from a run that dispatched and failed", - "packages/spec/src/contracts/automation-service.ts — AutomationResult.code, the closed union the two trigger-time refusals were added to", - "packages/spec/src/api/error-code-ledger.zod.ts — ADR-0112 registration of FLOW_DISABLED / FLOW_NO_START_NODE / FLOW_FAILED to @objectstack/runtime", + "packages/services/service-automation/src/engine.ts#disabled — the FLOW_NO_START_NODE exit; the disabled-flow exit stamps FLOW_DISABLED, and neither carries `status`, which is what lets a transport tell a never-dispatched exit from a run that dispatched and failed", + "packages/spec/src/contracts/automation-service.ts#AutomationResult — AutomationResult.code, the closed union the two trigger-time refusals were added to", + "packages/spec/src/api/error-code-ledger.zod.ts#FLOW_DISABLED — ADR-0112 registration of FLOW_DISABLED / FLOW_NO_START_NODE / FLOW_FAILED to @objectstack/runtime", "packages/client/CHANGELOG.md 17.1.0 (bc6434b resume, 9aa8890 disabled/no-start-node, 48032c9 ran-and-failed) — the three BREAKING changesets whose own status table this matrix is derived from", - "packages/runtime/src/domains/automation-trigger-route-status.test.ts, automation-trigger-paused-run.test.ts, endpoint-flow-dispatch-status.test.ts, actions-flow-dispatch-status.test.ts — the per-door pins" + "packages/runtime/src/domains/automation-trigger-route-status.test.ts#automation, automation-trigger-paused-run.test.ts, endpoint-flow-dispatch-status.test.ts, actions-flow-dispatch-status.test.ts — the per-door pins" ], "history": [ { @@ -1358,12 +1358,12 @@ ], "source": [ "docs/adr/0126-packaged-metadata-customization-model.md §4 (the generic ledger + row shape), §7.2 (the durable row replaces the process-local flowEnabled map; FLOW_DISABLED code reused, distinction rides the message)", - "packages/services/service-automation/src/engine.ts (toggleFlow — durable row FIRST), (describeDisabledFlow — the ledger message), (hydrateFlowActivations), (registerFlow does not re-arm a ledger-disabled flow — restart survival's other half), (flowLedgerDisabled projection)", + "packages/services/service-automation/src/engine.ts#toggleFlow (toggleFlow — durable row FIRST), (describeDisabledFlow — the ledger message), (hydrateFlowActivations), (registerFlow does not re-arm a ledger-disabled flow — restart survival's other half), (flowLedgerDisabled projection)", "packages/services/service-automation/src/plugin.ts (hydrate AFTER the boot flow pull — the ordering that makes the unbind stick)", - "packages/core/src/utils/metadata-activation-store.ts (org-carrying rows skipped on read), (setActive read-then-update; organization_id never written), (no delete on the engine slice)", - "packages/platform-objects/src/system/sys-metadata-activation.object.ts (apiMethods ['get','list'] — reads open, generic-data-API writes refused)", - "packages/services/service-automation/src/flow-activation-ledger.test.ts (engine-level pins: hydration unbinds what a PREVIOUS process disabled, ledger-disabled survives re-registration — the HTTP+real-restart path here is what those pins cannot cover)", - "examples/app-showcase/src/automation/flows/index.ts (UrgentTaskAlertFlow) + examples/app-showcase/objectstack.config.ts (com.example.showcase)", + "packages/core/src/utils/metadata-activation-store.ts#setActive (org-carrying rows skipped on read), (setActive read-then-update; organization_id never written), (no delete on the engine slice)", + "packages/platform-objects/src/system/sys-metadata-activation.object.ts#apiMethods (apiMethods ['get','list'] — reads open, generic-data-API writes refused)", + "packages/services/service-automation/src/flow-activation-ledger.test.ts#engine (engine-level pins: hydration unbinds what a PREVIOUS process disabled, ledger-disabled survives re-registration — the HTTP+real-restart path here is what those pins cannot cover)", + "examples/app-showcase/src/automation/flows/index.ts#UrgentTaskAlertFlow (UrgentTaskAlertFlow) + examples/app-showcase/objectstack.config.ts (com.example.showcase)", "#12438 (sweep) · Epic #12150", "cross-refs: automation.flow-toggle-kill-switch (the single-boot kill-switch half — not repeated here), automation.durable-suspend-restart (the cold-restart recipe + file-DB discipline)" ], @@ -1449,9 +1449,9 @@ ], "source": [ "docs/adr/0126-packaged-metadata-customization-model.md §7.3 (the subflow cascade: refused, callers named, attached to disable only)", - "packages/services/service-automation/src/engine.ts (packagedSubflowCallers — definition scan at disable time, subflow AND map via config.flowName, non-packaged and self skipped), (the guard, before the durable write; the thrown {code:'DELETE_RESTRICTED', status:409, subflowCallers})", - "packages/spec/src/api/errors.zod.ts (DELETE_RESTRICTED — standard 409 catalog member, no new code minted; its DELETE_ prefix fits per the #10243 ruling quoted at the throw site)", - "packages/services/service-automation/src/flow-activation-ledger.test.ts (the engine-level pins: names every caller, map counts, non-packaged and self do not guard, enable never guarded, no-callers lands)", + "packages/services/service-automation/src/engine.ts#packagedSubflowCallers (packagedSubflowCallers — definition scan at disable time, subflow AND map via config.flowName, non-packaged and self skipped), (the guard, before the durable write; the thrown {code:'DELETE_RESTRICTED', status:409, subflowCallers})", + "packages/spec/src/api/errors.zod.ts#DELETE_RESTRICTED (DELETE_RESTRICTED — standard 409 catalog member, no new code minted; its DELETE_ prefix fits per the #10243 ruling quoted at the throw site)", + "packages/services/service-automation/src/flow-activation-ledger.test.ts#engine (the engine-level pins: names every caller, map counts, non-packaged and self do not guard, enable never guarded, no-callers lands)", "examples/app-showcase/src/automation/flows/index.ts,707,795,816,1322,1339 (the three stock caller→child pairs)", "#12438 (sweep) · Epic #12150", "cross-ref: automation.packaged-flow-disable-durable (what a disable that LANDS must do); automation.setup-packaged-automation-board (the refusal rendered in the UI)" @@ -1550,12 +1550,12 @@ ], "source": [ "docs/adr/0126-packaged-metadata-customization-model.md §7.1 (mandatory new name, whole-definition copy, no linkage), §9 (no re-pointing, no ancestry), §1.3 (the shipped 'customize in Studio' promise the honest clause holds the clone to)", - "packages/runtime/src/flow-clone.ts (FLOW_CLONE_STATUS draft — and why it is deliberately not an off-switch), (FLOW_CLONE_MUTATED_FIELDS), (FLOW_CLONE_DROPPED_KEYS derived from spec), (FLOW_CLONE_NOTICE), (409 + name-taken message), (deep copy via structuredClone; _packageId et al. stripped)", - "packages/runtime/src/domains/automation.ts (the clone arm: body-first validation, mandatory name+label, 404/409 probes, registerFlow-only registration, no ancestry on the response), (clone in the manage_metadata write set)", - "packages/runtime/src/domains/automation-flow-clone.test.ts (route-level pins for the validation and refusal arms — a mocked-service pin; it cannot see the live dispatcher, the double-fire, or the restart — which is exactly the remainder this item drives)", + "packages/runtime/src/flow-clone.ts#FLOW_CLONE_STATUS (FLOW_CLONE_STATUS draft — and why it is deliberately not an off-switch), (FLOW_CLONE_MUTATED_FIELDS), (FLOW_CLONE_DROPPED_KEYS derived from spec), (FLOW_CLONE_NOTICE), (409 + name-taken message), (deep copy via structuredClone; _packageId et al. stripped)", + "packages/runtime/src/domains/automation.ts#clone (the clone arm: body-first validation, mandatory name+label, 404/409 probes, registerFlow-only registration, no ancestry on the response), (clone in the manage_metadata write set)", + "packages/runtime/src/domains/automation-flow-clone.test.ts#dispatcher (route-level pins for the validation and refusal arms — a mocked-service pin; it cannot see the live dispatcher, the double-fire, or the restart — which is exactly the remainder this item drives)", "packages/spec/src/automation/flow.zod.ts (machine-name regex)", "objectui packages/app-shell/src/views/studio-design/StudioDesignSurface.tsx (the Automations rail is package-scoped — the reachability half of the honest clause)", - "examples/app-showcase/src/automation/flows/index.ts (UrgentTaskAlertFlow; TaskDoneNotifyOwnerFlow → showcase_notify_owner)", + "examples/app-showcase/src/automation/flows/index.ts#UrgentTaskAlertFlow (UrgentTaskAlertFlow; TaskDoneNotifyOwnerFlow → showcase_notify_owner)", "#12438 (sweep) · Epic #12150", "cross-ref: access-security.packaged-flow-write-door-parity (owns the packaged-base write-door question this item's ordinariness clause must not be read as answering); automation.setup-packaged-automation-board (the clone dialog UI half)" ], @@ -1673,7 +1673,7 @@ "objectui packages/app-shell/src/views/setup/PackagedAutomationPage.tsx (529 clone-dialog validation,516 role=alert refusal rendering) + PackagedActionsSection.tsx (header: ⛔ no clone on actions) + packagedFlows.ts (isPackagedFlowItem + runtime-spine join)", "objectui packages/app-shell/src/services/builtinComponents.tsx (registerAppComponent ref 'automation:packaged') + views/ComponentNavView.tsx (ref resolution, no gate) + views/setup/PackagedAutomationPage.navContribution.test.tsx (ref→URL resolution — the mocked half of the nav clause)", "objectui packages/core/src/actions/actionErrorDetail.ts (error → error.message → message; details[] dropped)", - "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_packaged_automation → componentRef 'automation:packaged', landed by #12457; the entry's comment records why it lives in platform-objects, not service-automation) + setup-packaged-automation-nav.test.ts (the framework-half pin)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_packaged_automation (nav_packaged_automation → componentRef 'automation:packaged', landed by #12457; the entry's comment records why it lives in platform-objects, not service-automation) + setup-packaged-automation-nav.test.ts (the framework-half pin)", "packages/runtime/src/domains/automation.ts (the exact enablement 403 sentence)", "packages/platform-objects/src/system/sys-metadata-activation.object.ts (reads open)", "docs/adr/0054 (locator stability / UI-testability contract — the repo-ownership split recorded in fixtures)", diff --git a/docs/qa/platform-checklist/areas/cli.json b/docs/qa/platform-checklist/areas/cli.json index 61cab97b65..56ae7dcf2e 100644 --- a/docs/qa/platform-checklist/areas/cli.json +++ b/docs/qa/platform-checklist/areas/cli.json @@ -89,9 +89,9 @@ ], "traps": ["stale-dist", "stale-console-bundle", "shared-browser-tab"], "source": [ - "packages/cli/src/commands/dev.ts (resolveDevDatabase — dev's ONE resolution seam onto the shared matrix, carrying no fallback filename of its own; the persistent-default rationale; --fresh coverage note #5594; seed-admin idempotency contract; IPC bound-port report; #5148 staleness warning + rebuild-restart coordinator)", - "packages/runtime/src/resolve-project-database.ts (#6469 resolveProjectDatabaseUrl — the shared priority matrix explicit → env → memory driver → config-declared default datasource → unified /data/objectstack.db, the state-dir order homeDir/OS_HOME//.objectstack/~/.objectstack, and the legacy dev.db/standalone.db compat-read with its loud notice)", - "packages/cli/src/commands/unified-db-resolution.pin.test.ts (pins dev/start/migrate all resolving through that one seam)", + "packages/cli/src/commands/dev.ts#resolveDevDatabase (resolveDevDatabase — dev's ONE resolution seam onto the shared matrix, carrying no fallback filename of its own; the persistent-default rationale; --fresh coverage note #5594; seed-admin idempotency contract; IPC bound-port report; #5148 staleness warning + rebuild-restart coordinator)", + "packages/runtime/src/resolve-project-database.ts#resolveProjectDatabaseUrl (#6469 resolveProjectDatabaseUrl — the shared priority matrix explicit → env → memory driver → config-declared default datasource → unified /data/objectstack.db, the state-dir order homeDir/OS_HOME//.objectstack/~/.objectstack, and the legacy dev.db/standalone.db compat-read with its loud notice)", + "packages/cli/src/commands/unified-db-resolution.pin.test.ts#start (pins dev/start/migrate all resolving through that one seam)", ".claude/skills/dogfood-verification/SKILL.md §0–§1 (port isolation, health probe, fixed admin creds, /_console layout)" ], "history": [ @@ -170,9 +170,9 @@ ], "traps": ["stale-dist"], "source": [ - "packages/cli/src/commands/compile.ts (the full gate pipeline: Zod parse, #4409 author-time rule registry, #3786 unknown-key advisory, --json shapes, #3782 conversion-notice parity; the human-path catches ending in this.error() — oclif's ExitError 2 — while the --json branch answers through emitJson + this.exit(1)) + build.ts (alias)", - "packages/cli/src/utils/format.ts (CliExitCode = 0 | 1 — the narrowed emitJson/emitText exit slot, NOT a bound on oclif's own error exit)", - "packages/cli/src/utils/format.exit-code.test.ts (the #4873 pin: the defect was an argument in the wrong slot — timer.elapsed() landing in emitJson's positional exit code — and a number can no longer reach it at all)", + "packages/cli/src/commands/compile.ts#emitJson (the full gate pipeline: Zod parse, #4409 author-time rule registry, #3786 unknown-key advisory, --json shapes, #3782 conversion-notice parity; the human-path catches ending in this.error() — oclif's ExitError 2 — while the --json branch answers through emitJson + this.exit(1)) + build.ts (alias)", + "packages/cli/src/utils/format.ts#CliExitCode (CliExitCode = 0 | 1 — the narrowed emitJson/emitText exit slot, NOT a bound on oclif's own error exit)", + "packages/cli/src/utils/format.exit-code.test.ts#emitJson (the #4873 pin: the defect was an argument in the wrong slot — timer.elapsed() landing in emitJson's positional exit code — and a number can no longer reach it at all)", "cli.flag-command-error-ux (the sibling item that owns oclif's exit 2 — cross-referenced so the two never re-collide)", "api-backend.formula-gates, api-backend.enforce-or-remove-authoring-gates (gate content — cross-referenced, not duplicated)" ], @@ -270,7 +270,7 @@ "automated": { "kind": "unit", "ref": "packages/cli/src/utils/format.exit-code.test.ts" }, "source": [ "packages/cli/src/commands/migrate/index.ts + plan.ts (#2186 bare-command-is-plan; #3917 enforced never-mutates; occupancy warning) + apply.ts (--allow-destructive / --force / --yes / in_sync)", - "packages/cli/src/utils/format.ts (CliExitCode narrows the emitJson exit slot — the #4873 fix, commit 83df2fd)", + "packages/cli/src/utils/format.ts#CliExitCode (CliExitCode narrows the emitJson exit slot — the #4873 fix, commit 83df2fd)", "packages/cli/src/commands/migrate/ (the nine registered subcommands enumerated as variants)", "sibling item cli.migrate-duplicates-inventory (the duplicates subcommand's own contract — cross-referenced, not duplicated)" ], @@ -351,8 +351,8 @@ "traps": ["stale-dist", "wrong-persona"], "automated": { "kind": "unit", "ref": "packages/cli/src/commands/verify-tenancy-posture.test.ts" }, "source": [ - "packages/cli/src/commands/verify.ts (hardFailures sum, exit contract, resolveVerifyMultiTenant / #5262 / ADR-0105 D1, separate RLS stack rationale)", - "packages/verify/src/verify.ts (the six-member status union; 400-VALIDATION_FAILED→needs-fixture vs 5xx→create-failed classification)", + "packages/cli/src/commands/verify.ts#hardFailures (hardFailures sum, exit contract, resolveVerifyMultiTenant / #5262 / ADR-0105 D1, separate RLS stack rationale)", + "packages/verify/src/verify.ts#VALIDATION_FAILED (the six-member status union; 400-VALIDATION_FAILED→needs-fixture vs 5xx→create-failed classification)", "packages/verify/src/rls.ts (#1994 class: you can't write what you can't read)" ], "history": [ @@ -543,11 +543,11 @@ }, "traps": ["dispatcher-vs-hono-route", "stale-dist", "wrong-persona"], "source": [ - "packages/cli/src/commands/test.ts (the shipped `os test`: the #7363 lazy segment-directed glob with its prune list, `loadTestSuite`'s #6247 boundary parse, the per-scenario report, and the exit 0/1 summary)", - "packages/core/src/qa/runner.ts (scenario sequencing, `capture` + `{{var}}` interpolation, the assertion operators, setup/teardown semantics, the #7256 unevaluable-`contains` fix)", - "packages/core/src/qa/http-adapter.ts (the action-type switch — its case labels ARE the enum values; the record routes take their prefix from the one memoised `/discovery` probe per run, falling back to the RestApiConfigSchema + CrudEndpointsConfigSchema convention with a diagnostic that names the mount, #7848 / #7983)", - "packages/spec/src/qa/testing.zod.ts (TestSuiteSchema — the shape enforced at load; TestActionTypeSchema pinned above)", - "packages/spec/liveness/qa.json (the ADR-0049 ledger whose existence this item is coverage.json's mapping for — its dead `tags`/`requires` rows are why no scenario selection exists)", + "packages/cli/src/commands/test.ts#loadTestSuite (the shipped `os test`: the #7363 lazy segment-directed glob with its prune list, `loadTestSuite`'s #6247 boundary parse, the per-scenario report, and the exit 0/1 summary)", + "packages/core/src/qa/runner.ts#scenario (scenario sequencing, `capture` + `{{var}}` interpolation, the assertion operators, setup/teardown semantics, the #7256 unevaluable-`contains` fix)", + "packages/core/src/qa/http-adapter.ts#mount (the action-type switch — its case labels ARE the enum values; the record routes take their prefix from the one memoised `/discovery` probe per run, falling back to the RestApiConfigSchema + CrudEndpointsConfigSchema convention with a diagnostic that names the mount, #7848 / #7983)", + "packages/spec/src/qa/testing.zod.ts#TestSuiteSchema (TestSuiteSchema — the shape enforced at load; TestActionTypeSchema pinned above)", + "packages/spec/liveness/qa.json#tags (the ADR-0049 ledger whose existence this item is coverage.json's mapping for — its dead `tags`/`requires` rows are why no scenario selection exists)", "packages/cli/test/qa-suite-schema-load.test.ts, packages/cli/test/resolve-glob-lazy-walk.test.ts, packages/core/src/qa/runner.test.ts (the three unit pins — cited so a run knows what is already covered, NOT a substitute for driving a booted app)", "content/docs/deployment/cli.mdx §os test (the documented command contract)", "examples/app-showcase/qa/platform-smoke.test.json (the fixture suite this item drives)" @@ -747,9 +747,9 @@ ], "traps": ["destructive-in-place", "absence-inference"], "source": [ - "packages/cli/src/commands/migrate/meta.ts (applyMetaMigrations, composeSpecChanges, normalizeStackInput, MigrationFloorError, PROTOCOL_MAJOR; the declared flag surface and the two --out-guarded writeFileSync calls)", + "packages/cli/src/commands/migrate/meta.ts#writeFileSync (applyMetaMigrations, composeSpecChanges, normalizeStackInput, MigrationFloorError, PROTOCOL_MAJOR; the declared flag surface and the two --out-guarded writeFileSync calls)", "packages/spec/src/migrations/entries/retired-defs/ · retired-keys/ · semantic/ (the registries it replays)", - "the retirement messages across packages/spec that prescribe this command by name — the shared sentence in packages/spec/src/shared/retired-key.ts and its per-key sites (e.g. the BOOK_TRANSLATIONS_RETIRED text in packages/spec/src/system/book.zod.ts), pinned class-wide by packages/spec/src/shared/retired-key-migrate-sentence.test.ts", + "the retirement messages across packages/spec that prescribe this command by name — the shared sentence in packages/spec/src/shared/retired-key.ts and its per-key sites (e.g. the BOOK_TRANSLATIONS_RETIRED text in packages/spec/src/system/book.zod.ts#pinned), pinned class-wide by packages/spec/src/shared/retired-key-migrate-sentence.test.ts", "ADR-0087 (retirement registries and conversion routes)", "#4873 (--json exit-code honesty across migrate subcommands)", "#9529 (the 2026-08-18 reword: the messages promise a LIST of mechanical edits applied by hand, not an automatic rewrite)", @@ -841,11 +841,11 @@ "traps": ["hydration-race", "first-boot-cold-start"], "source": [ "content/docs/getting-started/quick-start.mdx ('How you verify' — `npx os validate` and `npx os dev --ui`, then open http://localhost:3000/_console/)", - "packages/create-objectstack/src/templates/blank/package.json (scripts.dev = `objectstack dev`, with no --ui — correct, see the chain below)", + "packages/create-objectstack/src/templates/blank/package.json#objectstack (scripts.dev = `objectstack dev`, with no --ui — correct, see the chain below)", "packages/cli/src/commands/serve.ts (`ui: Flags.boolean({..., default: true, allowNo: true })` — the console is default-ON here; `--no-ui` is the off switch)", "packages/cli/src/commands/dev.ts + (dev declares `ui` with NO default and forwards `...(flags.ui ? ['--ui'] : [])` — it only ever ADDS the flag, never forwards --no-ui, so an unflagged dev inherits serve's default-on)", "content/docs/deployment/cli.mdx ('--ui | Force Console UI on (already on by default in dev)') and ('--ui / --no-ui — Toggle Console UI at /_console/ (default on)')", - "packages/create-objectstack/src/template-registry.ts (TEMPLATES = { blank } only; RETIRED_TEMPLATES named for a named refusal)", + "packages/create-objectstack/src/template-registry.ts#TEMPLATES (TEMPLATES = { blank } only; RETIRED_TEMPLATES named for a named refusal)", "sibling item cli.scaffold-first-run (scaffold→install→validate→build→`os start --artifact`→health/ready; automated by .github/workflows/scaffold-e2e.yml)", "sibling item cli.dev-boot-contract (os dev --ui + /_console/ sign-in, driven against examples/app-showcase)", "sibling item studio-authoring.first-run-loop (the in-console authoring loop, once the console is reachable)" @@ -950,7 +950,7 @@ ], "traps": ["absence-inference", "stale-dist"], "source": [ - "packages/cli/src/commands/doctor.ts (class, flags -v/--scan-deprecations, run body; posture resolve + row; env sources row; NODE_ENV row; spec-workspace probe; ledger read; unique-scope withhold; MonorepoTreeScan, monorepoTreeSkipNotice, printMonorepoTreeSkip, findMissingTests, findDeprecatedUsages, their print sites; summary exit)", + "packages/cli/src/commands/doctor.ts#MonorepoTreeScan (class, flags -v/--scan-deprecations, run body; posture resolve + row; env sources row; NODE_ENV row; spec-workspace probe; ledger read; unique-scope withhold; MonorepoTreeScan, monorepoTreeSkipNotice, printMonorepoTreeSkip, findMissingTests, findDeprecatedUsages, their print sites; summary exit)", "packages/cli/src/commands/doctor-env-provenance.test.ts, doctor-tenancy-posture-report.test.ts, doctor-ledger-dir-authority.test.ts, doctor-ledger-posture-independence.test.ts, doctor-ledger-read-failure.test.ts, doctor-config-load-cause.test.ts, doctor-node-env-default.test.ts, doctor-unexamined-spec-tree.test.ts (the unit pins — they cover the row-building seams, none drives the CLI end-to-end, which is why this item carries no automated entry)", "sibling item cli.flag-command-error-ux (owns only `os doctor --help`; this item is the command's first functional coverage)", "sibling item cli.doctor-deprecation-scan (the --scan-deprecations flag's own item — cross-referenced, not duplicated)" @@ -1037,8 +1037,8 @@ ], "traps": ["absence-inference", "stale-dist"], "source": [ - "packages/cli/src/commands/doctor.ts (flag; scan block with the scanDir = /src, the per-hit warning row, the --verbose `→ replacement`, the #10680 reasoning comment, the two-branch hint and the clean-run success line; DEPRECATED_PATTERNS — retired-lookup pair; scanDeprecatedPatterns — absent-dir [],.ts-only walk +.test.ts filter; walkDir node_modules prune)", - "packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts (#10680's pin — the class-closing sweep that every `os …` hint doctor can PRINT resolves under src/commands/, plus six end-to-end cases over Doctor.run. Added to source at revision 3 because revision 2's knownGaps asserted this surface had no automated coverage of any kind)", + "packages/cli/src/commands/doctor.ts#scanDir (flag; scan block with the scanDir = /src, the per-hit warning row, the --verbose `→ replacement`, the #10680 reasoning comment, the two-branch hint and the clean-run success line; DEPRECATED_PATTERNS — retired-lookup pair; scanDeprecatedPatterns — absent-dir [],.ts-only walk +.test.ts filter; walkDir node_modules prune)", + "packages/cli/src/commands/doctor-deprecation-hint-commands.test.ts#Doctor (#10680's pin — the class-closing sweep that every `os …` hint doctor can PRINT resolves under src/commands/, plus six end-to-end cases over Doctor.run. Added to source at revision 3 because revision 2's knownGaps asserted this surface had no automated coverage of any kind)", "content/docs/protocol/backward-compatibility.mdx (the documented workflow, including the admission that the codemod command is not yet available)", "packages/cli/README.md §os doctor (documents -v and --scan-deprecations)", "packages/create-objectstack/src/templates/blank/ (the scaffold whose src/ the seeds land in)", @@ -1140,7 +1140,7 @@ ], "traps": ["stale-dist", "absence-inference"], "source": [ - "packages/cli/src/commands/migrate/duplicates.ts (the contract header encoding the 2026-08-16 maintainer ruling's five points; report interfaces; the cross-partition HAVING; the seam-answer guards; flags; read-only boot; no_sql_seam refusal)", + "packages/cli/src/commands/migrate/duplicates.ts#report (the contract header encoding the 2026-08-16 maintainer ruling's five points; report interfaces; the cross-partition HAVING; the seam-answer guards; flags; read-only boot; no_sql_seam refusal)", "packages/cli/src/commands/migrate/duplicates.contract.test.ts (the full JSON shape against a real sqlite), duplicates.pre-repair.test.ts (byte-identical DB + the #8686 repair measured destroying the evidence), duplicates.integration.test.ts, duplicates.probe-sql.test.ts — seam pins; none drives the oclif command end-to-end, hence no automated entry", "#8928 (the card and ruling), #8686 / #8844 (the closed producers whose damage this inventories)", "sibling item cli.migrate-plan-apply-json (lists duplicates as a variant; the scratch-DB boot recipe is shared)" @@ -1229,8 +1229,8 @@ "traps": ["dispatcher-vs-hono-route", "seed-data-thin", "wrong-persona"], "source": [ "packages/cli/src/commands/datasource/list-tables.ts (route doc, listing), introspect.ts (route doc, flags, the cwd jail, REVIEW passthrough), validate.ts (route doc, diff rendering + exit)", - "packages/rest/src/external-datasource-routes.ts + rest-route-ledger.ts (the served federation routes incl. POST …/external/validate) and external-datasource-routes-auth-guard.test.ts (the auth wall the token satisfies)", - "examples/app-showcase/src/system/datasources/showcase-external.datasource.ts + external-fixture.ts (the fixture datasource 'showcase_external')", + "packages/rest/src/external-datasource-routes.ts#routes + rest-route-ledger.ts (the served federation routes incl. POST …/external/validate) and external-datasource-routes-auth-guard.test.ts (the auth wall the token satisfies)", + "examples/app-showcase/src/system/datasources/showcase-external.datasource.ts#showcase_external + external-fixture.ts (the fixture datasource 'showcase_external')", "ADR-0015 (external datasource federation)", "sibling item cli.flag-command-error-ux (owned only the topic's --help until now)" ], @@ -1320,9 +1320,9 @@ ], "traps": ["stale-dist", "absence-inference"], "source": [ - "packages/cli/src/utils/extract-hook-body.ts (contract header, carrying the #10917 retirement note; FORBIDDEN_PATTERNS; CAPABILITY_PATTERNS with the #4391 removal note; #1876 free-identifier throw — the @capabilities override branch this item used to cite is retired and gone)", - "packages/cli/src/utils/lower-callables.ts (the warnings field, tryExtractBody's catch-all fallback-to-bundle — the seam the default path's silence flows from)", - "packages/cli/src/commands/compile.ts (the --strict-body gate, the needsBundle decision + skip line, the --json warnings key that excludes extraction warnings)", + "packages/cli/src/utils/extract-hook-body.ts#FORBIDDEN_PATTERNS (contract header, carrying the #10917 retirement note; FORBIDDEN_PATTERNS; CAPABILITY_PATTERNS with the #4391 removal note; #1876 free-identifier throw — the @capabilities override branch this item used to cite is retired and gone)", + "packages/cli/src/utils/lower-callables.ts#tryExtractBody (the warnings field, tryExtractBody's catch-all fallback-to-bundle — the seam the default path's silence flows from)", + "packages/cli/src/commands/compile.ts#needsBundle (the --strict-body gate, the needsBundle decision + skip line, the --json warnings key that excludes extraction warnings)", "content/docs/automation/hook-bodies.mdx (the documented default + --strict-body posture; the Capability inference section — the inference table, the body.capabilities route, and the callout recording the retired directive)", "packages/cli/src/utils/lower-callables.test.ts (the existing unit seam pin — cited, not a substitute for driving os build)", "sibling items records-forms (runtime sandbox side, body-runner.ts) and cli.build-own-contract (the build's own exit/output contract)" @@ -1415,9 +1415,9 @@ ], "traps": ["stale-dist", "seed-data-thin"], "source": [ - "packages/cli/src/commands/lint.ts (Severity; foldCoverageIssues; lintConfig's own rubric + the #4409 registry call with info→suggestion; flags; --json emit; disclosure; human exit; load-failure catch; --fix dry-run)", - "packages/cli/src/utils/collect-docs.ts (the ADR-0046 docs lint folded into the same issues stream)", - "packages/cli/src/utils/format.ts (CliExitCode and the #4873 doc note naming os lint as the duration-inside-the-payload exemplar)", + "packages/cli/src/commands/lint.ts#Severity (Severity; foldCoverageIssues; lintConfig's own rubric + the #4409 registry call with info→suggestion; flags; --json emit; disclosure; human exit; load-failure catch; --fix dry-run)", + "packages/cli/src/utils/collect-docs.ts#docs (the ADR-0046 docs lint folded into the same issues stream)", + "packages/cli/src/utils/format.ts#CliExitCode (CliExitCode and the #4873 doc note naming os lint as the duration-inside-the-payload exemplar)", "sibling item cli.build-own-contract (clause 3 is the build side of the registry-parity pair; its #3782 --json parity clause binds build↔validate, NOT lint)" ], "history": [ @@ -1502,12 +1502,12 @@ "destructive-in-place" ], "source": [ - "packages/cli/src/commands/plugin/build.ts — manifest read + named refusal; ManifestSchema.safeParse + formatZodErrors; the explicit id/version guard; packaging default and the Loaded line; entry resolution; the manifest-deps externalization; the manifest-deps package.json / pnpm-lock.yaml packing; computeIntegrity + the compiled manifest; the artifact write and its printed block", - "packages/cli/src/utils/osplugin.ts — MANIFEST_FILENAME = 'objectstack.plugin.json'; OSPLUGIN_EXT; computeIntegrity (manifest and SIGNATURE excluded, keys sorted); the ustar writer with mtime pinned to 0; readOspluginManifest", - "packages/cli/src/commands/plugin/publish.ts — artifact discovery; readOspluginManifest before any network call; the id/version refusal; the unsigned-artifact notice; the auth refusal", - "packages/spec/src/kernel/manifest.zod.ts — ManifestSchema id and version (both required, version regex-pinned); PluginPackagingSchema ('bundled' | 'manifest-deps', ADR-0025 §3.3); PluginRuntimeSchema (the trust tier, ADR-0025 §3.6)", - "packages/cli/src/commands/lint.ts — the protocol/missing-engines-range rule, its manifest scoping and its fix string", - "packages/rest/src/package-routes.ts — the server mirror (400 PACKAGE_MANIFEST_INVALID), recorded in knownGaps rather than scored here", + "packages/cli/src/commands/plugin/build.ts#formatZodErrors — manifest read + named refusal; ManifestSchema.safeParse + formatZodErrors; the explicit id/version guard; packaging default and the Loaded line; entry resolution; the manifest-deps externalization; the manifest-deps package.json / pnpm-lock.yaml packing; computeIntegrity + the compiled manifest; the artifact write and its printed block", + "packages/cli/src/utils/osplugin.ts#MANIFEST_FILENAME — MANIFEST_FILENAME = 'objectstack.plugin.json'; OSPLUGIN_EXT; computeIntegrity (manifest and SIGNATURE excluded, keys sorted); the ustar writer with mtime pinned to 0; readOspluginManifest", + "packages/cli/src/commands/plugin/publish.ts#version — artifact discovery; readOspluginManifest before any network call; the id/version refusal; the unsigned-artifact notice; the auth refusal", + "packages/spec/src/kernel/manifest.zod.ts#ManifestSchema — ManifestSchema id and version (both required, version regex-pinned); PluginPackagingSchema ('bundled' | 'manifest-deps', ADR-0025 §3.3); PluginRuntimeSchema (the trust tier, ADR-0025 §3.6)", + "packages/cli/src/commands/lint.ts#manifest — the protocol/missing-engines-range rule, its manifest scoping and its fix string", + "packages/rest/src/package-routes.ts#PACKAGE_MANIFEST_INVALID — the server mirror (400 PACKAGE_MANIFEST_INVALID), recorded in knownGaps rather than scored here", "content/docs/releases/v15.mdx (the release that dated objectstack lint's protocol/missing-engines-range) · ADR-0025 §3.3 / §3.6", "sibling items: platform-core.manifest-install-contract (the same manifest at the install boundary) · cli.build-own-contract and cli.lint-severity-exit-contract (the os build / os lint exit-code contracts this item leans on but does not re-prove)" ], @@ -1606,11 +1606,11 @@ "traps": ["stale-dist", "destructive-in-place"], "automated": { "kind": "unit", "ref": "packages/drivers/driver-sql/src/sql-driver-schema-drift.test.ts" }, "source": [ - "packages/spec/src/data/driver/common.zod.ts (SqlAutoMigrateSchema z.enum(['off','safe']) — 'Dev-only, loosen-only schema self-heal (#2186) … force-disabled under NODE_ENV=production') + sqlite.zod.ts (the authored key; the wasm not-honoured guidance)", - "packages/drivers/driver-sql/src/sql-driver.ts (SqlDriverConfig.autoMigrate docblock: non-destructive alters only), (default 'off'), (reconcileAndWarnDrift — the managed+safe gate, the production ignore warning verbatim, safe-only applyMigrationEntries allowDestructive:false, per-op auto-reconciled info line, post-reconcile re-detect, warn-once loop)", - "packages/drivers/driver-sql/src/schema-drift.ts (tightenNullSafeOnly: clean probe → safe, duplicates → blocked with a row report, old index left in place; ADR-0120 D4)", - "packages/cli/src/utils/storage-driver.ts (the ONE dev injection point for the fallback default datasource: isDev ? { autoMigrate: 'safe' } : {}) + packages/cli/src/commands/serve.ts (telemetry datasource: isDev ? 'safe' : undefined)", - "packages/drivers/driver-sql/src/sql-driver-schema-drift.test.ts + sql-driver-unique-tenancy.test.ts + sql-driver-index-drift.test.ts,322-342 (the pins: off-warns / safe-applies / clean-probe tighten / duplicates block)", + "packages/spec/src/data/driver/common.zod.ts#SqlAutoMigrateSchema (SqlAutoMigrateSchema z.enum(['off','safe']) — 'Dev-only, loosen-only schema self-heal (#2186) … force-disabled under NODE_ENV=production') + sqlite.zod.ts (the authored key; the wasm not-honoured guidance)", + "packages/drivers/driver-sql/src/sql-driver.ts#SqlDriverConfig (SqlDriverConfig.autoMigrate docblock: non-destructive alters only), (default 'off'), (reconcileAndWarnDrift — the managed+safe gate, the production ignore warning verbatim, safe-only applyMigrationEntries allowDestructive:false, per-op auto-reconciled info line, post-reconcile re-detect, warn-once loop)", + "packages/drivers/driver-sql/src/schema-drift.ts#tightenNullSafeOnly (tightenNullSafeOnly: clean probe → safe, duplicates → blocked with a row report, old index left in place; ADR-0120 D4)", + "packages/cli/src/utils/storage-driver.ts#isDev (the ONE dev injection point for the fallback default datasource: isDev ? { autoMigrate: 'safe' } : {}) + packages/cli/src/commands/serve.ts#isDev (telemetry datasource: isDev ? 'safe' : undefined)", + "packages/drivers/driver-sql/src/sql-driver-schema-drift.test.ts#driver + sql-driver-unique-tenancy.test.ts + sql-driver-index-drift.test.ts,322-342 (the pins: off-warns / safe-applies / clean-probe tighten / duplicates block)", "sibling items: cli.dev-boot-contract (the boot this rides), cli.migrate-plan-apply-json (the deliberate apply path — the ONLY door for destructive diffs; cross-referenced, not re-proven)" ], "history": [ diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json index ebb2320511..7c402bacb0 100644 --- a/docs/qa/platform-checklist/areas/dashboards.json +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -91,7 +91,7 @@ "source": [ "#3358 §3 (four stray-key variants all rejected with the named error)", "packages/spec/src/ui/dashboard.zod.ts (LEGACY key list + strict error map, ADR-0021 single-form cutover)", - "packages/spec/src/ui/chart.zod.ts (ChartConfigSchema wrong-layer guidance: dataset/aggregate/objectName/drillDown)" + "packages/spec/src/ui/chart.zod.ts#ChartConfigSchema (ChartConfigSchema wrong-layer guidance: dataset/aggregate/objectName/drillDown)" ], "history": [ { @@ -398,12 +398,12 @@ "stale-console-bundle" ], "source": [ - "packages/spec/src/ui/chart.zod.ts (ChartTypeSchema — 20 members; NOTE on trimmed variants and single-value synonyms)", + "packages/spec/src/ui/chart.zod.ts#ChartTypeSchema (ChartTypeSchema — 20 members; NOTE on trimmed variants and single-value synonyms)", "examples/app-showcase/src/ui/dashboards/chart-gallery.dashboard.ts", "examples/app-showcase/src/ui/datasets/chart-gallery.dataset.ts", - "examples/app-showcase/src/coverage.ts + examples/app-showcase/test/coverage.test.ts (SAME_AS_METRIC waiver)", - "packages/spec/src/ui/dashboard-compareto.test.ts (#5011 — compareTo converged on the executor's { kind, dimension? } contract; parses through the dashboard root; retired spellings rejected with the upgrade)", - "packages/spec/src/ui/dashboard.zod.ts (DashboardWidgetSchema compareTo slot — union-free strict object so its prescription reaches the wire)" + "examples/app-showcase/src/coverage.ts + examples/app-showcase/test/coverage.test.ts#SAME_AS_METRIC (SAME_AS_METRIC waiver)", + "packages/spec/src/ui/dashboard-compareto.test.ts#dashboard (#5011 — compareTo converged on the executor's { kind, dimension? } contract; parses through the dashboard root; retired spellings rejected with the upgrade)", + "packages/spec/src/ui/dashboard.zod.ts#DashboardWidgetSchema (DashboardWidgetSchema compareTo slot — union-free strict object so its prescription reaches the wire)" ], "history": [ { @@ -538,7 +538,7 @@ "seed-data-thin" ], "source": [ - "packages/spec/src/ui/report.zod.ts (ReportType enum; block type enum excludes joined)", + "packages/spec/src/ui/report.zod.ts#ReportType (ReportType enum; block type enum excludes joined)", "packages/spec/src/ui/dashboard.zod.ts (ADR-0021 dataset+dimensions+values single form)", "packages/spec/src/ui/dataset.zod.ts", "examples/app-showcase/src/ui/reports/index.ts (the four reports + Phase 2 tabular conversion note)", @@ -634,8 +634,8 @@ "source": [ "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts (#3773)", "packages/qa/dogfood/test/empty-group-bucket-parity.test.ts (#3839)", - "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts (dateRange + global filter levers)", - "examples/app-showcase/src/data/seed/index.ts (prospects carry no signed_on — the deliberate NULL population)" + "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts#dateRange (dateRange + global filter levers)", + "examples/app-showcase/src/data/seed/index.ts#signed_on (prospects carry no signed_on — the deliberate NULL population)" ], "history": [ { @@ -748,7 +748,7 @@ "source": [ "examples/app-showcase/src/ui/dashboards/revenue-pulse.dashboard.ts (framework#2501 / objectui#2578 acceptance fixture)", "examples/app-showcase/src/ui/dashboards/ops-dashboard.dashboard.ts", - "examples/app-showcase/src/data/seed/index.ts (sales_region + signed_on seeding, prospects unset by design)", + "examples/app-showcase/src/data/seed/index.ts#sales_region (sales_region + signed_on seeding, prospects unset by design)", "examples/app-showcase/src/coverage.ts (dashboard kind notes: revenue-pulse demonstrates dashboard-level filters)", "objectui packages/app-shell/src/views/metadata-admin/inspectors/DashboardWidgetInspector.tsx (Filter Bindings section — Apply toggle writes false for opt-out, field-override combo, data-testid widget-filter-binding-, patches draft.widgets[i].filterBindings; objectui#2586)", "objectui packages/plugin-dashboard/src/DashboardFilterBar.tsx (isDirty→Reset affordance RotateCcw, data-testid dashboard-filter-bar; SelectFilter {value,label} options + server GROUP BY via queryDataset, #2578 item 5)", @@ -836,7 +836,7 @@ ], "source": [ "packages/rest/src/rest-route-ledger.ts (reports family)", - "packages/plugins/plugin-reports/src/report-service.ts (canAccessReport #2980; unscheduleReport/listSchedules owner-gated in #6683)", + "packages/plugins/plugin-reports/src/report-service.ts#canAccessReport (canAccessReport #2980; unscheduleReport/listSchedules owner-gated in #6683)", "packages/platform-objects/src/audit/sys-saved-report.object.ts", "docs/plans/release-15.1-test-plan.md §A10 (#2980/#2981/#2975)" ], @@ -937,9 +937,9 @@ "wrong-persona" ], "source": [ - "packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts (widget ids, requiresService/requiresObject gates, globalFilters date range)", + "packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts#requiresService (widget ids, requiresService/requiresObject gates, globalFilters date range)", "packages/platform-objects/src/apps/dashboards/system.datasets.ts (the five sys_*_metrics datasets and their count measures)", - "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_system_overview → dashboard system_overview in the Setup app)" + "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_system_overview (nav_system_overview → dashboard system_overview in the Setup app)" ], "history": [ { @@ -1033,12 +1033,12 @@ "single-datapoint" ], "source": [ - "examples/app-showcase/src/data/analytics/showcase.cube.ts (the showcase_delivery cube — measures, dimensions, base table, showcase_project join, public:false)", - "examples/app-showcase/src/coverage.ts (analyticsCubes registration → src/data/analytics/showcase.cube.ts, served by /api/v1/analytics/*)", - "packages/runtime/src/domains/analytics.ts (route contract: POST /analytics/query, GET /analytics/meta[?cube], entry validation, ExecutionContext scoping #2852, handled:false 404 for an absent/stub slot #3891/#4000)", - "packages/spec/src/api/analytics.zod.ts (AnalyticsQueryRequestSchema bare shape + retiredKey query/format; meta response cubes[])", - "packages/services/service-analytics/src/analytics-service.ts (getMeta keys measures/dimensions as `${cube}.${key}`, returns all registry cubes)", - "packages/cli/src/commands/serve.ts (CAPABILITY_PROVIDERS.analytics → @objectstack/service-analytics, configKey analyticsCubes)" + "examples/app-showcase/src/data/analytics/showcase.cube.ts#showcase_project (the showcase_delivery cube — measures, dimensions, base table, showcase_project join, public:false)", + "examples/app-showcase/src/coverage.ts#analyticsCubes (analyticsCubes registration → src/data/analytics/showcase.cube.ts, served by /api/v1/analytics/*)", + "packages/runtime/src/domains/analytics.ts#cube (route contract: POST /analytics/query, GET /analytics/meta[?cube], entry validation, ExecutionContext scoping #2852, handled:false 404 for an absent/stub slot #3891/#4000)", + "packages/spec/src/api/analytics.zod.ts#AnalyticsQueryRequestSchema (AnalyticsQueryRequestSchema bare shape + retiredKey query/format; meta response cubes[])", + "packages/services/service-analytics/src/analytics-service.ts#getMeta (getMeta keys measures/dimensions as `${cube}.${key}`, returns all registry cubes)", + "packages/cli/src/commands/serve.ts#CAPABILITY_PROVIDERS (CAPABILITY_PROVIDERS.analytics → @objectstack/service-analytics, configKey analyticsCubes)" ], "history": [ { @@ -1137,11 +1137,11 @@ "ref": "packages/plugins/plugin-reports/src/report-service.test.ts — the dispatch describes: owner-scoped execution + fails-closed-without-resolver (#2980), csv attachment, subject template, cron/interval advance; the LIVE halves (dispatcher registration, the fail-closed row write on a real boot, the log arms) still need the run" }, "source": [ - "packages/plugins/plugin-reports/src/report-service.ts (dispatchDue: due filter on next_run_at; owner fail-closed with the verbatim #2849/#2980 refusal; format fork + CJK-safe filename; renderSubject; skipped arm), (nextRunAt — cron wins, schedule timezone, warned fallback), (advanceSchedule 'ok'/markSchedule), (scheduleReport — first next_run_at, eager cron validation, recipients required)", - "packages/plugins/plugin-reports/src/reports-plugin.ts (optional email + the no-email warn), (resolveOwnerContext: undefined — the recorded fail-closed posture pending ADR-0073 M2), (dispatcher: job-service arm 'reports.dispatch' or setInterval fallback, min 5s / default 60s)", + "packages/plugins/plugin-reports/src/report-service.ts#dispatchDue (dispatchDue: due filter on next_run_at; owner fail-closed with the verbatim #2849/#2980 refusal; format fork + CJK-safe filename; renderSubject; skipped arm), (nextRunAt — cron wins, schedule timezone, warned fallback), (advanceSchedule 'ok'/markSchedule), (scheduleReport — first next_run_at, eager cron validation, recipients required)", + "packages/plugins/plugin-reports/src/reports-plugin.ts#resolveOwnerContext (optional email + the no-email warn), (resolveOwnerContext: undefined — the recorded fail-closed posture pending ADR-0073 M2), (dispatcher: job-service arm 'reports.dispatch' or setInterval fallback, min 5s / default 60s)", "packages/plugins/plugin-reports/src/report-service.test.ts (the unit pins cited by clauses 3-5)", - "packages/cli/src/commands/serve.ts (CAPABILITY_PROVIDERS.reports → @objectstack/plugin-reports — the dispatcher runs on stock boots)", - "packages/plugins/plugin-email/src/email-plugin.ts (stock provider 'log' → LogTransport, 'mail will NOT be sent' — why delivery evidence is a log line)", + "packages/cli/src/commands/serve.ts#CAPABILITY_PROVIDERS (CAPABILITY_PROVIDERS.reports → @objectstack/plugin-reports — the dispatcher runs on stock boots)", + "packages/plugins/plugin-email/src/email-plugin.ts#provider (stock provider 'log' → LogTransport, 'mail will NOT be sent' — why delivery evidence is a log line)", "docs/plans/release-15.1-test-plan.md §A10 (reports IDOR / 定时报表 RLS — #2980/#2981/#2975; the ownership half was imported as dashboards.saved-report-ownership, this item is the 定时报表-RLS dispatch leg that never was)", "cross-ref: dashboards.saved-report-ownership (the /reports* owner-isolation surfaces, including schedule routes #6683)" ], diff --git a/docs/qa/platform-checklist/areas/i18n.json b/docs/qa/platform-checklist/areas/i18n.json index 37543e5e43..b7f612656d 100644 --- a/docs/qa/platform-checklist/areas/i18n.json +++ b/docs/qa/platform-checklist/areas/i18n.json @@ -76,11 +76,11 @@ "#3358 §7", "#3362", "#3354", - "examples/app-showcase/objectstack.config.ts (supportedLocales)", - "packages/spec/src/system/translation.zod.ts (TRANSLATION_KEY_GUIDANCE — `notifications` is the retired object-first dialect; notifications have no translation group)", + "examples/app-showcase/objectstack.config.ts#supportedLocales (supportedLocales)", + "packages/spec/src/system/translation.zod.ts#TRANSLATION_KEY_GUIDANCE (TRANSLATION_KEY_GUIDANCE — `notifications` is the retired object-first dialect; notifications have no translation group)", "#3403 (platform-side assignment notifications moved to app-authored flows; pinned by packages/plugins/plugin-audit/src/audit-writers.test.ts)", - "examples/app-showcase/src/automation/flows/index.ts (the notify_assignee node whose literal cfg.title this item now asserts)", - "packages/services/service-automation/src/builtin/notify-node.ts (title = interpolate(cfg.title) — no locale lookup on the flow notify path)", + "examples/app-showcase/src/automation/flows/index.ts#notify_assignee (the notify_assignee node whose literal cfg.title this item now asserts)", + "packages/services/service-automation/src/builtin/notify-node.ts#title (title = interpolate(cfg.title) — no locale lookup on the flow notify path)", "#7684 (the decision this revision implements), #7640 (the run that surfaced it)" ], "history": [ @@ -153,8 +153,8 @@ "traps": ["stale-console-bundle"], "source": [ "#3358 §7", - "packages/spec/src/system/translation.zod.ts (metadataForms group + resolveMetadataFormLabels convention)", - "scripts/check-i18n-coverage.mjs (platform metadata-form baseline is platform-objects-owned)", + "packages/spec/src/system/translation.zod.ts#metadataForms (metadataForms group + resolveMetadataFormLabels convention)", + "scripts/check-i18n-coverage.mjs#baseline (platform metadata-form baseline is platform-objects-owned)", "scripts/i18n-coverage-baseline.json (the frozen per-config untranslated counts this item's mixed-language clause is judged against)", "#7640 (i18n area run — the residual EN strings that exposed the clause/ratchet collision), #7686" ], @@ -298,10 +298,10 @@ ], "traps": ["stale-console-bundle", "hydration-race", "wrong-panel", "dispatcher-vs-hono-route"], "source": [ - "packages/spec/src/system/translation.zod.ts (translationDataShape — the authoritative group vocabulary: objects/_views/_actions/_sections, apps.navigation, messages, globalActions, dashboards, pages, settings, metadataForms, settingsCommon)", + "packages/spec/src/system/translation.zod.ts#translationDataShape (translationDataShape — the authoritative group vocabulary: objects/_views/_actions/_sections, apps.navigation, messages, globalActions, dashboards, pages, settings, metadataForms, settingsCommon)", "examples/app-showcase/src/system/translations/index.ts (full-column coverage rationale)", - "packages/services/service-i18n/src/i18n-service-plugin.ts (GET /i18n/locales | /translations/:locale | /labels/:object/:locale; { success, data } envelope #3636/#3675; resolveObjectFieldLabels nested shape #3778/#3833; the plugin mount and the dispatcher /i18n domain serve the same routes interchangeably)", - "packages/services/service-i18n/src/file-i18n-adapter.ts (getLocales / getTranslations — unloaded locale → {}; fallbackLocale applies per-KEY in t(), not to the bulk route)", + "packages/services/service-i18n/src/i18n-service-plugin.ts#i18n (GET /i18n/locales | /translations/:locale | /labels/:object/:locale; { success, data } envelope #3636/#3675; resolveObjectFieldLabels nested shape #3778/#3833; the plugin mount and the dispatcher /i18n domain serve the same routes interchangeably)", + "packages/services/service-i18n/src/file-i18n-adapter.ts#getLocales (getLocales / getTranslations — unloaded locale → {}; fallbackLocale applies per-KEY in t(), not to the bulk route)", "packages/services/service-i18n/src/i18n-route-ledger.ts (the three routes, conformance-guarded #3636)", "content/docs/ui/translations.mdx", "scripts/check-i18n-coverage.mjs + scripts/i18n-coverage-baseline.json (frozen-debt ratchet)", @@ -396,7 +396,7 @@ ], "traps": ["dispatcher-vs-hono-route"], "source": [ - "packages/spec/src/system/translation.zod.ts (TRANSLATION_HISTORY, LEGACY_OBJECT_FIRST_KEYS, TRANSLATION_KEY_GUIDANCE, strict shapes at both doors — #4001, #3778, #4667)", + "packages/spec/src/system/translation.zod.ts#TRANSLATION_HISTORY (TRANSLATION_HISTORY, LEGACY_OBJECT_FIRST_KEYS, TRANSLATION_KEY_GUIDANCE, strict shapes at both doors — #4001, #3778, #4667)", "packages/spec/src/shared/strict-object.ts (surface/aliases/guidance rejection contract)" ], "history": [ @@ -466,7 +466,7 @@ "--write printing 'regenerated' while writing nothing (the historical unbuilt-CLI shape) is a FAIL" ], "source": [ - "scripts/check-i18n-bundles.mjs (#4804 undeclared-key verdict, #5217 prerequisite check, #4690 self-test rationale; the merge-mode regenerate hint that makes value edits green by design)", + "scripts/check-i18n-bundles.mjs#undeclared (#4804 undeclared-key verdict, #5217 prerequisite check, #4690 self-test rationale; the merge-mode regenerate hint that makes value edits green by design)", "scripts/check-i18n-coverage.mjs (#3370 declared-surface ratchet, #5862 prerequisite check)", "scripts/i18n-coverage-baseline.json", "AGENTS.md §Documentation Guardrails, the translations/*.generated.ts row (merge-mode extract ⇒ values are hand-written and expected to differ; adding or dropping keys is the drift)", diff --git a/docs/qa/platform-checklist/areas/identity-auth.json b/docs/qa/platform-checklist/areas/identity-auth.json index 4c3608bd58..ff128bfd06 100644 --- a/docs/qa/platform-checklist/areas/identity-auth.json +++ b/docs/qa/platform-checklist/areas/identity-auth.json @@ -65,9 +65,9 @@ "traps": ["hydration-race"], "source": [ "#3358 §6", - "packages/spec/src/system/auth-config.zod.ts (ssoOnlyMode + OS_AUTH_SSO_ONLY, break-glass endpoint stays enabled)", - "packages/spec/src/api/auth-endpoints.zod.ts (AuthFeaturesConfigSchema.ssoEnforced description)", - "packages/spec/src/kernel/public-auth-features.ts (ssoEnforced: login-surface flag; LoginForm hides password form + self-registration, break-glass link remains)" + "packages/spec/src/system/auth-config.zod.ts#ssoOnlyMode (ssoOnlyMode + OS_AUTH_SSO_ONLY, break-glass endpoint stays enabled)", + "packages/spec/src/api/auth-endpoints.zod.ts#AuthFeaturesConfigSchema (AuthFeaturesConfigSchema.ssoEnforced description)", + "packages/spec/src/kernel/public-auth-features.ts#ssoEnforced (ssoEnforced: login-surface flag; LoginForm hides password form + self-registration, break-glass link remains)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358", "ref": "#3358" }, @@ -136,9 +136,9 @@ "source": [ "#3358 §6", "#3408", - "examples/app-showcase/src/security/seed-approval-demo.ts (PHONE_DEMO_USER)", - "packages/spec/src/system/auth-config.zod.ts (phoneNumber plugin: unique phone_number + phone_number_verified columns)", - "packages/spec/src/kernel/public-auth-features.ts (phoneNumber gates sys_user.actions.create_user.params.phoneNumber — #2871)" + "examples/app-showcase/src/security/seed-approval-demo.ts#PHONE_DEMO_USER (PHONE_DEMO_USER)", + "packages/spec/src/system/auth-config.zod.ts#phoneNumber (phoneNumber plugin: unique phone_number + phone_number_verified columns)", + "packages/spec/src/kernel/public-auth-features.ts#phoneNumber (phoneNumber gates sys_user.actions.create_user.params.phoneNumber — #2871)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "initial import from #3358; encoded the #3408 silent-seed-failure as an explicit negative", "ref": "#3358" }, @@ -262,12 +262,12 @@ "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts" }, "traps": ["hydration-race", "dispatcher-vs-hono-route", "wrong-persona"], "source": [ - "packages/spec/src/system/auth-config.zod.ts (AuthPluginConfigSchema: phoneNumber/twoFactor/deviceAuthorization/magicLink/passkeys; socialProviders; oidcProviders; EmailAndPasswordConfigSchema)", - "packages/spec/src/api/auth-endpoints.zod.ts (AuthEndpointPaths; AuthFeaturesConfigSchema; device-flow response schemas)", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (BETTER_AUTH_MOUNTED_SURFACE: the live change-email/delete-user + /.well-known/* rows; auth-plugin.ts mounts the two discovery docs at app root)", - "packages/spec/src/kernel/public-auth-features.ts (flag semantics, gated inputs, the objectui#2513 known gap, and PUBLIC_AUTH_FEATURES_NOT_ADVERTISED — the reserved-but-unserved record for magicLink/passkeys)", + "packages/spec/src/system/auth-config.zod.ts#AuthPluginConfigSchema (AuthPluginConfigSchema: phoneNumber/twoFactor/deviceAuthorization/magicLink/passkeys; socialProviders; oidcProviders; EmailAndPasswordConfigSchema)", + "packages/spec/src/api/auth-endpoints.zod.ts#AuthEndpointPaths (AuthEndpointPaths; AuthFeaturesConfigSchema; device-flow response schemas)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#BETTER_AUTH_MOUNTED_SURFACE (BETTER_AUTH_MOUNTED_SURFACE: the live change-email/delete-user + /.well-known/* rows; auth-plugin.ts mounts the two discovery docs at app root)", + "packages/spec/src/kernel/public-auth-features.ts#PUBLIC_AUTH_FEATURES_NOT_ADVERTISED (flag semantics, gated inputs, the objectui#2513 known gap, and PUBLIC_AUTH_FEATURES_NOT_ADVERTISED — the reserved-but-unserved record for magicLink/passkeys)", "packages/qa/dogfood/test/oidc-authorization-code-flow.dogfood.test.ts", - "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts (cookie-lane 2FA completion pinned end-to-end — sign-in → twoFactorRedirect + two-factor cookie → verify-totp → session — plus lockout counting/reset/lock-at-threshold/lazy-expiry/admin-unlock; also the arming precedent: OS_AUTH_TWO_FACTOR=true must precede bootStack)" + "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts#cookie (cookie-lane 2FA completion pinned end-to-end — sign-in → twoFactorRedirect + two-factor cookie → verify-totp → session — plus lockout counting/reset/lock-at-threshold/lazy-expiry/admin-unlock; also the arming precedent: OS_AUTH_TWO_FACTOR=true must precede bootStack)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new matrix item: per-method sign-in proof with both-sides gate checks and advertisement parity, grounded in the spec's plugin config + public feature registry", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -354,10 +354,10 @@ "traps": ["wrong-persona", "dispatcher-vs-hono-route"], "source": [ "packages/qa/dogfood/test/delegated-admin-invite.dogfood.test.ts (ADR-0105 D8 / #3697; the escalation chain the role cap blocks)", - "packages/spec/src/identity/organization.zod.ts (InvitationSchema, InvitationStatus enum)", - "packages/rest/src/rest-route-ledger.ts (GET /api/v1/security/my-delegable-scope — security.describeDelegableScope, ADR-0090 D12 / ADR-0105 D8, self-scoped read half of the delegated-admin gate)", - "packages/spec/src/contracts/security-service.ts (DelegableScope: isTenantAdmin, scopes, placeableBusinessUnitIds, assignablePositions — no invitation-role field, which is why the scope read cannot be the invite picker's allowlist)", - "packages/spec/src/kernel/public-auth-features.ts (organization feature gates sys_invitation invite/cancel/resend actions)" + "packages/spec/src/identity/organization.zod.ts#InvitationSchema (InvitationSchema, InvitationStatus enum)", + "packages/rest/src/rest-route-ledger.ts#describeDelegableScope (GET /api/v1/security/my-delegable-scope — security.describeDelegableScope, ADR-0090 D12 / ADR-0105 D8, self-scoped read half of the delegated-admin gate)", + "packages/spec/src/contracts/security-service.ts#DelegableScope (DelegableScope: isTenantAdmin, scopes, placeableBusinessUnitIds, assignablePositions — no invitation-role field, which is why the scope read cannot be the invite picker's allowlist)", + "packages/spec/src/kernel/public-auth-features.ts#sys_invitation (organization feature gates sys_invitation invite/cancel/resend actions)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item: invitation scope gates and lifecycle, pinned to the delegated-admin-invite dogfood test", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -475,12 +475,12 @@ "traps": ["wrong-persona", "shared-browser-tab"], "source": [ "packages/spec/src/system/auth-config.zod.ts (admin plugin: endpoint list, sys_user role/banned/ban_reason/ban_expires, sys_session.impersonated_by)", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (BETTER_AUTH_MOUNTED_SURFACE admin/* rows: list-users, create-user, set-role, remove-user, revoke-user-session(s))", - "packages/plugins/plugin-auth/src/admin-user-endpoints.ts (create-user resolvePassword: explicit password wins over generatePassword — #3031/#3033; leaves sys_user + credential sys_account)", - "packages/plugins/plugin-security/src/security-plugin.ts (§A5 #3023 EXEMPTION: __referentialFieldClear owner_id-null cascade rides a server-derived context, the owner-anchor guard must not veto it) + security-plugin.test.ts '[#3023] … engine referential FK clear … is exempt'", - "packages/spec/src/kernel/public-auth-features.ts (admin flag gates the sys_user lifecycle actions; SCIM forces it on — ADR-0071)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#BETTER_AUTH_MOUNTED_SURFACE (BETTER_AUTH_MOUNTED_SURFACE admin/* rows: list-users, create-user, set-role, remove-user, revoke-user-session(s))", + "packages/plugins/plugin-auth/src/admin-user-endpoints.ts#resolvePassword (create-user resolvePassword: explicit password wins over generatePassword — #3031/#3033; leaves sys_user + credential sys_account)", + "packages/plugins/plugin-security/src/security-plugin.ts#__referentialFieldClear (§A5 #3023 EXEMPTION: __referentialFieldClear owner_id-null cascade rides a server-derived context, the owner-anchor guard must not veto it) + security-plugin.test.ts '[#3023] … engine referential FK clear … is exempt'", + "packages/spec/src/kernel/public-auth-features.ts#sys_user (admin flag gates the sys_user lifecycle actions; SCIM forces it on — ADR-0071)", "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts", - "packages/plugins/plugin-auth/src/session-of-record.test.ts (better-auth answers /get-session with HTTP 200 + a JSON null body when the session is gone — NOT 401; a status-only assertion would pass against a fully revoked session)" + "packages/plugins/plugin-auth/src/session-of-record.test.ts#body (better-auth answers /get-session with HTTP 200 + a JSON null body when the session is gone — NOT 401; a status-only assertion would pass against a fully revoked session)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item: admin lifecycle operations with persistence, enforcement, attribution and both-sides gate checks", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -568,9 +568,9 @@ ], "traps": ["wrong-persona", "shared-browser-tab"], "source": [ - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (GET list-sessions=auth.sessions.list, POST revoke-session=auth.sessions.revoke, revoke-other-sessions=auth.sessions.revokeOthers, revoke-sessions=auth.sessions.revokeAll)", - "packages/platform-objects/src/identity/sys-session.object.ts (mine view filter user_id={current_user_id}; all_sessions admin view; revoked_at/revoke_reason fields ADR-0069 D4; revoke_session action recordIdParam:'token'; apiMethods ['get','list'] — writes 405 before 403, #1591/ADR-0092 D2)", - "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_sessions → Setup Sessions, objectName sys_session)" + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#revokeOthers (GET list-sessions=auth.sessions.list, POST revoke-session=auth.sessions.revoke, revoke-other-sessions=auth.sessions.revokeOthers, revoke-sessions=auth.sessions.revokeAll)", + "packages/platform-objects/src/identity/sys-session.object.ts#user_id (mine view filter user_id={current_user_id}; all_sessions admin view; revoked_at/revoke_reason fields ADR-0069 D4; revoke_session action recordIdParam:'token'; apiMethods ['get','list'] — writes 405 before 403, #1591/ADR-0092 D2)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_sessions (nav_sessions → Setup Sessions, objectName sys_session)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: owner-scoped active-session list (RLS) + per-primitive revoke semantics (revoke-session by token, revoke-other-sessions keeps current) + admin all-sessions view with revoked_at/revoke_reason, grounded in the auth route ledger and sys_session object (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -651,8 +651,8 @@ "source": [ "objectui apps/console/src/pages/developer/AgentConnectSection.tsx (ADR-0036 Phase 2b: POST /api/v1/keys mint, show-once dialog, x-api-key connect steps)", "packages/runtime/src/route-ledger.ts (POST /keys → client keys.create)", - "packages/platform-objects/src/identity/sys-api-key.object.ts (revoke_api_key/restore_api_key actions PATCH /api/v1/data/sys_api_key/{id} bodyExtra revoked; mine view user_id={current_user_id} vs all_keys; hashed hidden `key`, visible `prefix`; apiMethods ['get','list'])", - "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_api_keys requiredPermissions ['manage_platform_settings'] — admin all-view)" + "packages/platform-objects/src/identity/sys-api-key.object.ts#bodyExtra (revoke_api_key/restore_api_key actions PATCH /api/v1/data/sys_api_key/{id} bodyExtra revoked; mine view user_id={current_user_id} vs all_keys; hashed hidden `key`, visible `prefix`; apiMethods ['get','list'])", + "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_api_keys (nav_api_keys requiredPermissions ['manage_platform_settings'] — admin all-view)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: personal API key mint (show-once, POST /api/v1/keys) → authenticate-as-owner via x-api-key → revoke 401 → restore 200, plus mine-view vs admin all-view scoping, grounded in AgentConnectSection + sys_api_key object (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" } @@ -735,8 +735,8 @@ "traps": ["hydration-race", "stale-console-bundle"], "source": [ "objectui apps/console/src/pages/system/ProfilePage.tsx (updateUser name/image; useUpload avatar; PasswordCard changePassword vs setInitialPassword gated on hasLocalPassword; email immutable; data-testids profile-avatar-file/-upload-btn/-remove-btn)", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /api/v1/auth/update-user=auth.updateUser, POST /api/v1/auth/change-password=auth.changePassword, GET /api/v1/auth/get-session=auth.me)", - "packages/platform-objects/src/identity/sys-account.object.ts (previous_password_hashes ring — ADR-0069 D1 reuse-prevention backs change-password)" + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#updateUser (POST /api/v1/auth/update-user=auth.updateUser, POST /api/v1/auth/change-password=auth.changePassword, GET /api/v1/auth/get-session=auth.me)", + "packages/platform-objects/src/identity/sys-account.object.ts#previous_password_hashes (previous_password_hashes ring — ADR-0069 D1 reuse-prevention backs change-password)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: self-service name + avatar (real storage write, persists) and password change with current-password verification, plus the passwordless set-initial branch, grounded in ProfilePage.tsx + the auth route ledger (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" } @@ -821,13 +821,13 @@ ], "traps": ["wrong-persona", "dispatcher-vs-hono-route", "hydration-race"], "source": [ - "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_organization recordId {current_org_id}, ADR-0081; Teams/Invitations always mounted per ADR-0081 D1)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_organization (nav_organization recordId {current_org_id}, ADR-0081; Teams/Invitations always mounted per ADR-0081 D1)", "packages/plugins/plugin-auth/src/auth-route-ledger.ts (organization family: update-member-role, remove-member, update, create-team, add-team-member, list-members/teams/invitations, get-active-member, get-full-organization)", - "packages/spec/src/identity/membership-role.ts (BUILTIN_MEMBERSHIP_ROLES / BUILTIN_MEMBERSHIP_ROLE_OPTIONS — THE role vocabulary: owner/admin/delegated_admin/member, ADR-0108; 'nothing widens these at boot any more')", + "packages/spec/src/identity/membership-role.ts#BUILTIN_MEMBERSHIP_ROLES (BUILTIN_MEMBERSHIP_ROLES / BUILTIN_MEMBERSHIP_ROLE_OPTIONS — THE role vocabulary: owner/admin/delegated_admin/member, ADR-0108; 'nothing widens these at boot any more')", "docs/adr/0108-membership-grade-is-not-a-capability-channel.md (why the list is closed: a grade decides what you can REACH, never a bundle of what you may do)", - "packages/qa/dogfood/test/membership-role-vocabulary.dogfood.test.ts (both enforced selects offer exactly the four; a declared position or PermissionSet name is refused at better-auth's role check — ROLE_NOT_FOUND — before any insert)", - "packages/platform-objects/src/identity/sys-member.object.ts + sys-invitation.object.ts (role select options: [...BUILTIN_MEMBERSHIP_ROLE_OPTIONS])", - "packages/platform-objects/src/identity/sys-team-member.object.ts (add_team_member/remove_team_member actions → organization/add-team-member; unique team_id+user_id; requiresFeature organization)" + "packages/qa/dogfood/test/membership-role-vocabulary.dogfood.test.ts#PermissionSet (both enforced selects offer exactly the four; a declared position or PermissionSet name is refused at better-auth's role check — ROLE_NOT_FOUND — before any insert)", + "packages/platform-objects/src/identity/sys-member.object.ts#BUILTIN_MEMBERSHIP_ROLE_OPTIONS + sys-invitation.object.ts (role select options: [...BUILTIN_MEMBERSHIP_ROLE_OPTIONS])", + "packages/platform-objects/src/identity/sys-team-member.object.ts#team_id (add_team_member/remove_team_member actions → organization/add-team-member; unique team_id+user_id; requiresFeature organization)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: Setup Organization page {current_org_id} resolution (ADR-0081) with Members/Invitations/Teams tabs, update-member-role (4-name vocab)/remove-member/rename, create-team + add-team-member → sys_team_member rows, non-admin refused server-side (PENDING-GAPS §B). Teams membership deep-tested in identity-auth.teams-bu-membership; org-member management stays here", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -903,12 +903,12 @@ ], "traps": ["wrong-persona", "seed-data-thin", "hydration-race"], "source": [ - "packages/platform-objects/src/identity/sys-team-member.object.ts (add_team_member/remove_team_member → organization/add-team-member|remove-team-member; unique team_id+user_id)", - "packages/platform-objects/src/identity/sys-business-unit.object.ts (canonical BU tree ADR-0057 D2; kind enum; parent_business_unit_id self-ref; org_chart tree view; managedBy 'platform' — writable over the data API)", - "packages/platform-objects/src/identity/sys-business-unit-member.object.ts (user↔BU placement: function_in_business_unit member/lead/deputy, is_primary)", - "docs/qa/platform-checklist/areas/access-security.json (access-security.scope-depth-asymmetry — the depth matrix this cross-references for the tree-widening geometry)", - "examples/app-showcase/src/security/sharing-rules.ts (`share_new_inquiries_with_field_ops` — the shipped BU-consuming geometry: expands the bu_field_ops subtree onto showcase_inquiry)", - "examples/app-showcase/src/data/seed/index.ts (the sys_business_unit tree is seeded with explicit ids; user↔unit membership — sys_business_unit_member — and position assignments are NOT seeded, they stay runtime admin actions)" + "packages/platform-objects/src/identity/sys-team-member.object.ts#team_id (add_team_member/remove_team_member → organization/add-team-member|remove-team-member; unique team_id+user_id)", + "packages/platform-objects/src/identity/sys-business-unit.object.ts#parent_business_unit_id (canonical BU tree ADR-0057 D2; kind enum; parent_business_unit_id self-ref; org_chart tree view; managedBy 'platform' — writable over the data API)", + "packages/platform-objects/src/identity/sys-business-unit-member.object.ts#function_in_business_unit (user↔BU placement: function_in_business_unit member/lead/deputy, is_primary)", + "docs/qa/platform-checklist/areas/access-security.json#access (access-security.scope-depth-asymmetry — the depth matrix this cross-references for the tree-widening geometry)", + "examples/app-showcase/src/security/sharing-rules.ts#share_new_inquiries_with_field_ops (`share_new_inquiries_with_field_ops` — the shipped BU-consuming geometry: expands the bu_field_ops subtree onto showcase_inquiry)", + "examples/app-showcase/src/data/seed/index.ts#sys_business_unit (the sys_business_unit tree is seeded with explicit ids; user↔unit membership — sys_business_unit_member — and position assignments are NOT seeded, they stay runtime admin actions)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: team membership rows (create-team/add/remove) + child business-unit creation and user placement on the sys_business_unit tree, with a scope-geometry-consumes-the-tree clause cross-referencing access-security.scope-depth-asymmetry (PENDING-GAPS §C). Org-member management lives in identity-auth.org-membership-team-management", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -984,9 +984,9 @@ ], "traps": ["dispatcher-vs-hono-route", "wrong-persona"], "source": [ - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (BETTER_AUTH_MOUNTED_SURFACE: POST /api/v1/auth/request-password-reset, POST /api/v1/auth/reset-password, GET /api/v1/auth/reset-password/:token — the LIVE routes; AuthEndpointPaths.forgetPassword='/forget-password' is the divergent spec name)", - "packages/plugins/plugin-email/src/transports/index.ts (EMAIL_TRANSPORT_PROVIDERS 'log' → LogTransport — the dev capture transport)", - "packages/platform-objects/src/identity/sys-account.object.ts (previous_password_hashes ring — ADR-0069 D1 reuse-prevention)" + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#BETTER_AUTH_MOUNTED_SURFACE (BETTER_AUTH_MOUNTED_SURFACE: POST /api/v1/auth/request-password-reset, POST /api/v1/auth/reset-password, GET /api/v1/auth/reset-password/:token — the LIVE routes; AuthEndpointPaths.forgetPassword='/forget-password' is the divergent spec name)", + "packages/plugins/plugin-email/src/transports/index.ts#EMAIL_TRANSPORT_PROVIDERS (EMAIL_TRANSPORT_PROVIDERS 'log' → LogTransport — the dev capture transport)", + "packages/platform-objects/src/identity/sys-account.object.ts#previous_password_hashes (previous_password_hashes ring — ADR-0069 D1 reuse-prevention)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: forgot-password round trip (request-password-reset → dev-transport token capture → reset-password), old-password-refused/new-works, single-use + expiry refusals, anti-enumeration, with the token-capture dev-mail dependency recorded as a knownGap (PENDING-GAPS §B)", "ref": "claude/platform-test-checklist-ocwugl" } @@ -1075,12 +1075,12 @@ ], "traps": ["dispatcher-vs-hono-route", "wrong-persona", "hydration-race"], "source": [ - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (oauth-provider family, requires oidcProvider: oauth2/create-client=oauth.applications.register, get-client, consent=oauth.consent, get-consents) + (POST /api/v1/auth/sys-oauth-application/register — the console's session-required registration wrapper) + BETTER_AUTH_MOUNTED_SURFACE rows for oauth2/authorize, oauth2/token, oauth2/register (DCR)", - "packages/plugins/plugin-auth/src/auth-manager.ts (resolveOidcProviderEnabled: OS_OIDC_PROVIDER_ENABLED ?? config.oidcProvider ?? readMcpServerEnabledEnv — the default-ON chain, #2698) + (resolveDcrEnabled follows the same MCP default) + (oauthProvider plugin registration; consentPage = console /oauth/consent, loginPage = console /login)", - "packages/types/src/env.ts (isMcpServerEnabled — unset means TRUE; explicit false/0/off/no opts out)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#BETTER_AUTH_MOUNTED_SURFACE (oauth-provider family, requires oidcProvider: oauth2/create-client=oauth.applications.register, get-client, consent=oauth.consent, get-consents) + (POST /api/v1/auth/sys-oauth-application/register — the console's session-required registration wrapper) + BETTER_AUTH_MOUNTED_SURFACE rows for oauth2/authorize, oauth2/token, oauth2/register (DCR)", + "packages/plugins/plugin-auth/src/auth-manager.ts#resolveOidcProviderEnabled (resolveOidcProviderEnabled: OS_OIDC_PROVIDER_ENABLED ?? config.oidcProvider ?? readMcpServerEnabledEnv — the default-ON chain, #2698) + (resolveDcrEnabled follows the same MCP default) + (oauthProvider plugin registration; consentPage = console /oauth/consent, loginPage = console /login)", + "packages/types/src/env.ts#isMcpServerEnabled (isMcpServerEnabled — unset means TRUE; explicit false/0/off/no opts out)", "objectui apps/console/src/App.tsx (/oauth/consent → OAuthConsentPage) + apps/console/src/pages/auth/OAuthConsentPage.tsx", - "packages/platform-objects/src/identity/sys-oauth-consent.object.ts (row implies consent for listed scopes — consent_given removed; apiEnabled:false so verify via get-consents, not the data API)", - "packages/platform-objects/src/identity/sys-oauth-application.object.ts + setup-nav.contributions.ts (nav_oauth_apps → Setup OAuth Applications)" + "packages/platform-objects/src/identity/sys-oauth-consent.object.ts#apiEnabled (row implies consent for listed scopes — consent_given removed; apiEnabled:false so verify via get-consents, not the data API)", + "packages/platform-objects/src/identity/sys-oauth-application.object.ts#OAuth + setup-nav.contributions.ts (nav_oauth_apps → Setup OAuth Applications)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: OAuth client registration (secret once) + authorization-code consent loop (approve mints tokens + consent record, deny mints none, recorded consent short-circuits), mine-view scoped; blocked(fixture) pending a configured oidcProvider flow (PENDING-GAPS §C)", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -1152,9 +1152,9 @@ ], "traps": ["wrong-persona", "dispatcher-vs-hono-route", "hydration-race"], "source": [ - "packages/platform-objects/src/identity/sys-account.object.ts (link_social type:'url' → /api/v1/auth/sign-in/social?provider=&callbackURL=; unlink_account → /api/v1/auth/unlink-account accountId=row id; mine view user_id={current_user_id} vs all_links; provider options; apiMethods ['get','list'])", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST link-social=auth.accounts.linkSocial, GET list-accounts=auth.accounts.list, POST unlink-account=auth.accounts.unlink)", - "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_accounts → 'Identity Links', objectName sys_account)" + "packages/platform-objects/src/identity/sys-account.object.ts#user_id (link_social type:'url' → /api/v1/auth/sign-in/social?provider=&callbackURL=; unlink_account → /api/v1/auth/unlink-account accountId=row id; mine view user_id={current_user_id} vs all_links; provider options; apiMethods ['get','list'])", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#linkSocial (POST link-social=auth.accounts.linkSocial, GET list-accounts=auth.accounts.list, POST unlink-account=auth.accounts.unlink)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_accounts (nav_accounts → 'Identity Links', objectName sys_account)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: social/OIDC account linking round-trip → sys_account mine-view row, unlink removal, provider-less honest degradation; blocked(fixture) pending a configured IdP (PENDING-GAPS §C)", "ref": "claude/platform-test-checklist-ocwugl" } @@ -1243,8 +1243,8 @@ "traps": ["wrong-persona", "seed-data-thin"], "source": [ "objectui packages/app-shell/src/views/identityImport.ts (IdentityPasswordPolicy 'auto'|'none'|'invite'|'temporary'; wraps ImportWizard onto POST /api/v1/auth/admin/import-users; ≤500-row batches; one-time passwords response-only, never persisted; upsert idempotent on email/phone)", - "packages/plugins/plugin-auth/src/admin-user-endpoints.ts (POST /api/v1/auth/admin/import-users — platform-admin-gated login-capable account creation; explicit-password/generatePassword resolution)", - "packages/plugins/plugin-auth/src/admin-import-users.ts (the `auto` per-row plan: invite where email- or SMS-deliverable, temporary only otherwise) + auth-manager.ts,4074 (isEmailServiceAvailable / isPhoneOtpDeliverable — the two gates that decide it)" + "packages/plugins/plugin-auth/src/admin-user-endpoints.ts#explicit (POST /api/v1/auth/admin/import-users — platform-admin-gated login-capable account creation; explicit-password/generatePassword resolution)", + "packages/plugins/plugin-auth/src/admin-import-users.ts#plan (the `auto` per-row plan: invite where email- or SMS-deliverable, temporary only otherwise) + auth-manager.ts,4074 (isEmailServiceAvailable / isPhoneOtpDeliverable — the two gates that decide it)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: admin CSV identity import with password-policy matrix (auto/temporary/invite/none), imported-user sign-in, upsert idempotency, response-only one-time passwords, non-admin denied, grounded in objectui identityImport.ts + admin-user-endpoints.ts (PENDING-GAPS §G)", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -1327,14 +1327,14 @@ ], "traps": ["dispatcher-vs-hono-route", "absence-inference"], "source": [ - "packages/plugins/plugin-auth/src/auth-manager.ts (twoFactor: twoFactorFromEnv ?? pluginConfig.twoFactor ?? false — resolved once at construction) + (features.twoFactor, same resolution)", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /two-factor/enable, SDK row) + (POST /two-factor/get-totp-uri in BETTER_AUTH_MOUNTED_SURFACE — publication, not liveness, #7735)", - "packages/plugins/plugin-auth/src/auth-route-ledger.conformance.test.ts (endpoint EXISTENCE is already pinned at LEDGERED_PLUGIN_CONFIG twoFactor:true — behavior is this item's gap, not existence)", - "packages/platform-objects/src/identity/sys-two-factor.object.ts (enable_two_factor resultDialog: totpURI qrcode + backupCodes code-list + acknowledge; the 'shown only once' copy)", - "packages/platform-objects/src/identity/sys-user.object.ts (the navigable variant: no resultDialog, successMessage only; the engine-can't-render-it-yet admission)", - "packages/spec/src/ui/action.zod.ts (resultDialog: dot paths into result.data; renderer contract is a SHOULD to objectui, no in-repo consumer)", - "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts (the hand-rolled RFC 6238 helper to copy — no OTP dependency exists or may be added) + (arming must precede bootStack) + (enable → totpURI → secret extraction precedent)", - "packages/platform-objects/src/apps/account.app.ts (mounts sys_inbox_message/sys_member/sys_account/sys_session/sys_api_key/sys_oauth_application — sys_two_factor absent, which is why the browser lane is blocked)" + "packages/plugins/plugin-auth/src/auth-manager.ts#twoFactor (twoFactor: twoFactorFromEnv ?? pluginConfig.twoFactor ?? false — resolved once at construction) + (features.twoFactor, same resolution)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#BETTER_AUTH_MOUNTED_SURFACE (POST /two-factor/enable, SDK row) + (POST /two-factor/get-totp-uri in BETTER_AUTH_MOUNTED_SURFACE — publication, not liveness, #7735)", + "packages/plugins/plugin-auth/src/auth-route-ledger.conformance.test.ts#LEDGERED_PLUGIN_CONFIG (endpoint EXISTENCE is already pinned at LEDGERED_PLUGIN_CONFIG twoFactor:true — behavior is this item's gap, not existence)", + "packages/platform-objects/src/identity/sys-two-factor.object.ts#resultDialog (enable_two_factor resultDialog: totpURI qrcode + backupCodes code-list + acknowledge; the 'shown only once' copy)", + "packages/platform-objects/src/identity/sys-user.object.ts#resultDialog (the navigable variant: no resultDialog, successMessage only; the engine-can't-render-it-yet admission)", + "packages/spec/src/ui/action.zod.ts#resultDialog (resultDialog: dot paths into result.data; renderer contract is a SHOULD to objectui, no in-repo consumer)", + "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts#secret (the hand-rolled RFC 6238 helper to copy — no OTP dependency exists or may be added) + (arming must precede bootStack) + (enable → totpURI → secret extraction precedent)", + "packages/platform-objects/src/apps/account.app.ts#sys_inbox_message (mounts sys_inbox_message/sys_member/sys_account/sys_session/sys_api_key/sys_oauth_application — sys_two_factor absent, which is why the browser lane is blocked)" ], "history": [ { "revision": 1, "date": "2026-08-20", "change": "authored in the scoped scan-functionality (扫描功能) coverage sweep: the TOTP 'scan the QR' enrollment lifecycle had only its challenge gate covered (one clause of identity-auth.auth-method-matrix). This item takes the reveal half: enable's { totpURI, backupCodes } shape, the resultDialog path contract, the get-totp-uri re-reveal probe, and the recorded no-QR gap on the only navigable surface", "ref": "claude/new-session-0pv25p" } @@ -1410,9 +1410,9 @@ "traps": ["dispatcher-vs-hono-route", "absence-inference"], "source": [ "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /two-factor/verify-totp, SDK row)", - "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts (the verify-to-activate SETUP precedent this item promotes to a tested contract: enrolment confirmed through the session path, TOTP_NOT_ENABLED posture for unverified enrolments) + (isSignIn decides lockout bookkeeping) + (completing enrolment rotates the session token)", - "packages/platform-objects/src/identity/sys-two-factor.object.ts (verified: defaultValue TRUE — the declared default the probe interrogates)", - "packages/plugins/plugin-auth/src/auth-schema-config.ts (AUTH_TWO_FACTOR_SCHEMA: four renamed fields, verified unmapped-because-unrenamed) + AUTH_TWO_FACTOR_USER_FIELDS (twoFactorEnabled → two_factor_enabled)", + "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts#confirmed (the verify-to-activate SETUP precedent this item promotes to a tested contract: enrolment confirmed through the session path, TOTP_NOT_ENABLED posture for unverified enrolments) + (isSignIn decides lockout bookkeeping) + (completing enrolment rotates the session token)", + "packages/platform-objects/src/identity/sys-two-factor.object.ts#defaultValue (verified: defaultValue TRUE — the declared default the probe interrogates)", + "packages/plugins/plugin-auth/src/auth-schema-config.ts#AUTH_TWO_FACTOR_SCHEMA (AUTH_TWO_FACTOR_SCHEMA: four renamed fields, verified unmapped-because-unrenamed) + AUTH_TWO_FACTOR_USER_FIELDS (twoFactorEnabled → two_factor_enabled)", "packages/plugins/plugin-auth/src/auth-manager.ts (arming default false, resolved once at construction)" ], "history": [ @@ -1498,9 +1498,9 @@ "traps": ["dispatcher-vs-hono-route", "absence-inference"], "source": [ "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /two-factor/generate-backup-codes, POST /two-factor/verify-backup-code — SDK rows)", - "packages/platform-objects/src/identity/sys-two-factor.object.ts (regenerate_backup_codes: password param, one-shot resultDialog, 'previous codes stop working immediately') + (backup_codes storage declaration)", - "packages/platform-objects/src/identity/sys-user.object.ts (generate_backup_codes on the navigable surface: successMessage only, NO resultDialog — the recorded lockout path)", - "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts (the arming + challenge-cookie rig this item reuses: beginChallenge/cookieHeader; TOTP helper)" + "packages/platform-objects/src/identity/sys-two-factor.object.ts#resultDialog (regenerate_backup_codes: password param, one-shot resultDialog, 'previous codes stop working immediately') + (backup_codes storage declaration)", + "packages/platform-objects/src/identity/sys-user.object.ts#successMessage (generate_backup_codes on the navigable surface: successMessage only, NO resultDialog — the recorded lockout path)", + "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts#beginChallenge (the arming + challenge-cookie rig this item reuses: beginChallenge/cookieHeader; TOTP helper)" ], "history": [ { "revision": 1, "date": "2026-08-20", "change": "authored in the scoped scan-functionality (扫描功能) coverage sweep: backup codes had zero coverage — spend/single-use/regenerate-invalidates were asserted nowhere, and the no-reveal regenerate on the navigable sys_user surface (a self-lockout path) was unrecorded", "ref": "claude/new-session-0pv25p" } @@ -1584,9 +1584,9 @@ "traps": ["dispatcher-vs-hono-route", "absence-inference"], "source": [ "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /two-factor/disable, SDK row)", - "packages/platform-objects/src/identity/sys-user.object.ts (disable_two_factor: password param, visible only while two_factor_enabled) + packages/platform-objects/src/identity/sys-two-factor.object.ts (the sys_two_factor variant, same endpoint)", - "packages/plugins/plugin-auth/src/auth-manager.ts (computeAuthGate: MFA_REQUIRED after the grace window; mfa_required_at stamped lazily, no clearing write anywhere in the source; fail-open) + (isAuthGateActive)", - "packages/plugins/plugin-auth/src/auth-plugin.ts (mfa_required forces plugins.twoFactor on — 'otherwise gated users would have no way to comply' — the not-stranded contract the last clause tests)", + "packages/platform-objects/src/identity/sys-user.object.ts#two_factor_enabled (disable_two_factor: password param, visible only while two_factor_enabled) + packages/platform-objects/src/identity/sys-two-factor.object.ts#sys_two_factor (the sys_two_factor variant, same endpoint)", + "packages/plugins/plugin-auth/src/auth-manager.ts#computeAuthGate (computeAuthGate: MFA_REQUIRED after the grace window; mfa_required_at stamped lazily, no clearing write anywhere in the source; fail-open) + (isAuthGateActive)", + "packages/plugins/plugin-auth/src/auth-plugin.ts#twoFactor (mfa_required forces plugins.twoFactor on — 'otherwise gated users would have no way to comply' — the not-stranded contract the last clause tests)", "packages/qa/dogfood/test/two-factor-lockout.dogfood.test.ts (arming + challenge rig + TOTP helper precedent, as on the sibling items)" ], "history": [ @@ -1675,10 +1675,10 @@ "source": [ "objectui apps/console/src/components/SetupRoute.tsx (one URL, two surfaces — first-run wizard vs platform-admin deep link, objectui#2794) + setupEntry.ts (the latched verdict: fresh only from an unauthenticated probe)", "objectui apps/console/src/pages/auth/SetupPage.tsx (renders only at hasOwner:false; creates owner + names the auto-provisioned personal org) + (why both exits are FULL-PAGE navigations — objectui#4181) + handleSubmit (rename-not-create, refreshOrganizations poll, slug guard)", - "packages/plugins/plugin-auth/src/auth-plugin.ts (bootstrap-status exempt from the auth wall) + (the route: hasOwner from dataEngine.count('sys_user'))", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (GET /api/v1/auth/bootstrap-status = auth.bootstrapStatus, objectstack-mount)", - "packages/plugins/plugin-auth/src/auth-manager.ts (the bypass's contract comment) + (the before-hook: isBootstrapCreation flips disableSignUp for this request — [#11767] fixed the inert probe) + (isBootstrapCreation: human rows, fail-closed on a full page)", - "packages/plugins/plugin-auth/src/audience-posture.ts (isHumanUserRow) + (decideAudienceAdmission isBootstrap arm) + (undeclared audience ⇒ invite_only, maintainer ruling 2026-08-24)", + "packages/plugins/plugin-auth/src/auth-plugin.ts#dataEngine (bootstrap-status exempt from the auth wall) + (the route: hasOwner from dataEngine.count('sys_user'))", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#bootstrapStatus (GET /api/v1/auth/bootstrap-status = auth.bootstrapStatus, objectstack-mount)", + "packages/plugins/plugin-auth/src/auth-manager.ts#isBootstrapCreation (the bypass's contract comment) + (the before-hook: isBootstrapCreation flips disableSignUp for this request — [#11767] fixed the inert probe) + (isBootstrapCreation: human rows, fail-closed on a full page)", + "packages/plugins/plugin-auth/src/audience-posture.ts#isHumanUserRow (isHumanUserRow) + (decideAudienceAdmission isBootstrap arm) + (undeclared audience ⇒ invite_only, maintainer ruling 2026-08-24)", "packages/cli/src/commands/dev.ts,339 (seed-admin defaults ON — why a zero-user boot needs --no-seed-admin)" ], "history": [ @@ -1765,9 +1765,9 @@ "enumSource": { "file": "packages/spec/src/system/auth-config.zod.ts", "export": "AUDIENCE_POSTURES", "expect": 3 }, "traps": ["dispatcher-vs-hono-route", "auth-state-leak", "absence-inference"], "source": [ - "packages/plugins/plugin-auth/src/audience-posture.ts (the single owner of audience admission: resolveAudience default invite_only; decideAudienceAdmission — operator/provider exempt, bootstrap admit, invitation carve-out, posture semantics; SELF_REGISTRATION_CLOSED / EMAIL_DOMAIN_NOT_ALLOWED; pinned domain rules in the module doc)", - "packages/plugins/plugin-auth/src/auth-manager.ts (readDisableSignUpEnv: OS_AUTH_SIGNUP_ENABLED wins, then OS_DISABLE_SIGNUP) + (effective disableSignUp: ssoOnly forces true, else env ?? config) + (the sign-up route raises the audience refusal BEFORE the anti-enumeration shield — measured swallow documented in place) +,5040 (getPublicConfig: disableSignUp NOT forced by posture, features.audiencePosture advertised)", - "packages/spec/src/system/auth-config.zod.ts (AUDIENCE_POSTURES const — invite_only/email_domain/open, the named enum backing posture z.enum) + (AudienceConfigSchema on AuthConfigSchema — config-only, no env knob)", + "packages/plugins/plugin-auth/src/audience-posture.ts#resolveAudience (the single owner of audience admission: resolveAudience default invite_only; decideAudienceAdmission — operator/provider exempt, bootstrap admit, invitation carve-out, posture semantics; SELF_REGISTRATION_CLOSED / EMAIL_DOMAIN_NOT_ALLOWED; pinned domain rules in the module doc)", + "packages/plugins/plugin-auth/src/auth-manager.ts#readDisableSignUpEnv (readDisableSignUpEnv: OS_AUTH_SIGNUP_ENABLED wins, then OS_DISABLE_SIGNUP) + (effective disableSignUp: ssoOnly forces true, else env ?? config) + (the sign-up route raises the audience refusal BEFORE the anti-enumeration shield — measured swallow documented in place) +,5040 (getPublicConfig: disableSignUp NOT forced by posture, features.audiencePosture advertised)", + "packages/spec/src/system/auth-config.zod.ts#AUDIENCE_POSTURES (AUDIENCE_POSTURES const — invite_only/email_domain/open, the named enum backing posture z.enum) + (AudienceConfigSchema on AuthConfigSchema — config-only, no env knob)", "objectui apps/console/src/pages/auth/RegisterPage.tsx,69-88 (probe /auth/config, bounce to /login when disableSignUp; server-side gate named as the source of truth in its own header)", "objectui apps/console/src/pages/auth/LoginPage.tsx,136,298 (signUpDisabled from /auth/config; registerUrl withheld when disabled)" ], @@ -1853,9 +1853,9 @@ ], "traps": ["eventual-consistency", "auth-state-leak", "hydration-race"], "source": [ - "packages/plugins/plugin-auth/src/auth-manager.ts (requireEmailVerification wiring — [#11739]: a self-registration-permitting posture FORCES it on; otherwise config passthrough) + (emailVerification block: sendOnSignUp/sendOnSignIn/autoSignInAfterVerification/expiresIn passthrough; sendVerificationEmail via template auth.verify_email, failures thrown into the log) + (getPublicConfig mirrors the forced flag)", - "packages/plugins/plugin-auth/src/audience-posture.ts (entry validation refuses the permitting-posture + explicit-false contradiction)", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /api/v1/auth/send-verification-email = auth.sendVerificationEmail) + (GET /api/v1/auth/verify-email = auth.verifyEmail)", + "packages/plugins/plugin-auth/src/auth-manager.ts#requireEmailVerification (requireEmailVerification wiring — [#11739]: a self-registration-permitting posture FORCES it on; otherwise config passthrough) + (emailVerification block: sendOnSignUp/sendOnSignIn/autoSignInAfterVerification/expiresIn passthrough; sendVerificationEmail via template auth.verify_email, failures thrown into the log) + (getPublicConfig mirrors the forced flag)", + "packages/plugins/plugin-auth/src/audience-posture.ts#entry (entry validation refuses the permitting-posture + explicit-false contradiction)", + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#sendVerificationEmail (POST /api/v1/auth/send-verification-email = auth.sendVerificationEmail) + (GET /api/v1/auth/verify-email = auth.verifyEmail)", "objectui apps/console/src/App.tsx (/verify-email + /verify-email-prompt routes)", "objectui apps/console/src/pages/auth/VerifyEmailPage.tsx (consumes ?token= via the POST variant — GET 302s, POST returns JSON so the SPA controls the post-verify UX)", "objectui apps/console/src/pages/auth/VerifyEmailPromptPage.tsx (shown after sign-up or an EMAIL_NOT_VERIFIED-blocked sign-in; resend via useAuth().sendVerificationEmail)", @@ -1946,9 +1946,9 @@ "objectui packages/app-shell/src/layout/CurrentOrganizationIndicator.tsx (objectui#5287: read-only name for exactly-one membership, gated on postureHasOrgWall — single posture renders nothing by design; no click target)", "objectui packages/app-shell/src/hooks/useTenancyPosture.ts (postureHasOrgWall restated locally for bundle size, spec-parity test-locked)", "objectui packages/app-shell/src/providers/MetadataProvider.tsx (objectui#4486: an org change drops the whole metadata cache — one organization's metadata never survives into another organization's reads; the reloading switch paths and the SPA-internal path both covered)", - "packages/plugins/plugin-auth/src/auth-route-ledger.ts (POST /api/v1/auth/organization/set-active = organizations.setActive, requires organization)", - "packages/plugins/plugin-auth/src/auth-manager.ts (multiOrgEnabled = postureEnforcesWall(effectiveTenancyPosture()) — the SAME call the org-create gate makes, #5233/#5261) + area (features advertised)", - "packages/types/src/env.ts (resolveTenancyPosture: OS_TENANCY_POSTURE, invalid value refuses boot; unset falls back to `single` unless legacy multi-org env)" + "packages/plugins/plugin-auth/src/auth-route-ledger.ts#setActive (POST /api/v1/auth/organization/set-active = organizations.setActive, requires organization)", + "packages/plugins/plugin-auth/src/auth-manager.ts#multiOrgEnabled (multiOrgEnabled = postureEnforcesWall(effectiveTenancyPosture()) — the SAME call the org-create gate makes, #5233/#5261) + area (features advertised)", + "packages/types/src/env.ts#resolveTenancyPosture (resolveTenancyPosture: OS_TENANCY_POSTURE, invalid value refuses boot; unset falls back to `single` unless legacy multi-org env)" ], "history": [ { "revision": 1, "date": "2026-08-30", "change": "authored in the 2026-08-30 coverage sweep (angle 1): the workspace-switch surface had no item. CORRECTED against source from the register's 'single membership → indicator' hypothesis: the indicator is posture-gated (postureHasOrgWall — group/isolated only), so the stock `single` boot renders NO org chrome at all and the whole matrix needs an OS_TENANCY_POSTURE boot; encoded the three-cell affordance matrix, the deliberate full-document reload, the #4486 cache-drop read-follow proof, group-posture labeling, and the forged-switch refusal", "ref": "#sweep-2026-08-30" } diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json index c6bad7b95f..98b655a8ed 100644 --- a/docs/qa/platform-checklist/areas/integration-system.json +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -85,9 +85,9 @@ }, "source": [ "docs/plans/release-15.1-test-plan.md §B1 (#2994/#3062)", - "packages/spec/src/integration/connector.zod.ts (provider/providerConfig/auth keys + DeclarativeConnectorEntrySchema cross-field rejections, exact messages)", + "packages/spec/src/integration/connector.zod.ts#providerConfig (provider/providerConfig/auth keys + DeclarativeConnectorEntrySchema cross-field rejections, exact messages)", "packages/spec/src/integration/connector-provider.ts (factory contract; adopt-declared-name; throw ⇒ hard boot error)", - "packages/services/service-automation/src/engine.ts (registerConnector origin 'declarative'; getConnectorDescriptors shape)", + "packages/services/service-automation/src/engine.ts#registerConnector (registerConnector origin 'declarative'; getConnectorDescriptors shape)", "packages/runtime/src/route-ledger.ts (GET /automation/connectors, POST /automation/:name/trigger, GET /automation/:name/runs)", "examples/app-showcase/src/system/connectors/index.ts + src/automation/flows/index.ts (the fixtures)" ], @@ -171,9 +171,9 @@ ], "source": [ "docs/plans/release-15.1-test-plan.md §B3 (#3049)", - "packages/spec/src/integration/connector-provider-errors.ts (CONNECTOR_UPSTREAM_UNAVAILABLE marker; structural isConnectorUpstreamUnavailable)", - "packages/services/service-automation/src/plugin.ts (DECLARATIVE_RETRY_BASE_MS=5000 / DECLARATIVE_RETRY_MAX_MS=300000; degradedInstances backoff + reload reconcile; boot fatal vs reload skip)", - "packages/services/service-automation/src/engine.ts (registerDegradedConnector; getConnectorDegradedReason feeding the dispatch refusal)", + "packages/spec/src/integration/connector-provider-errors.ts#CONNECTOR_UPSTREAM_UNAVAILABLE (CONNECTOR_UPSTREAM_UNAVAILABLE marker; structural isConnectorUpstreamUnavailable)", + "packages/services/service-automation/src/plugin.ts#DECLARATIVE_RETRY_BASE_MS (DECLARATIVE_RETRY_BASE_MS=5000 / DECLARATIVE_RETRY_MAX_MS=300000; degradedInstances backoff + reload reconcile; boot fatal vs reload skip)", + "packages/services/service-automation/src/engine.ts#registerDegradedConnector (registerDegradedConnector; getConnectorDegradedReason feeding the dispatch refusal)", "packages/services/service-automation/src/connector-degrade-cause.test.ts + degraded-register-cause.test.ts (unit pins)" ], "history": [ @@ -255,7 +255,7 @@ "source": [ "docs/plans/release-15.1-test-plan.md §B4 (#3059)", "packages/connectors/connector-mcp/src/mcp-provider.ts (#3055 policy: default deny, strict-equality allowlist, both messages)", - "examples/app-showcase/objectstack.config.ts (declarativeStdio: ['node'] opt-in) + src/system/connectors/index.ts (the 'remove that option and boot fails loudly — try it' fixture note)" + "examples/app-showcase/objectstack.config.ts#declarativeStdio (declarativeStdio: ['node'] opt-in) + src/system/connectors/index.ts (the 'remove that option and boot fails loudly — try it' fixture note)" ], "history": [ { @@ -333,9 +333,9 @@ ], "source": [ "docs/plans/release-15.1-test-plan.md §B5 (#3024)", - "packages/services/service-automation/src/plugin.ts (createPackageFileLoader — all three exact error strings, #3016)", - "packages/spec/src/integration/connector.zod.ts (providerConfig.spec three-form doc) + connector-provider.ts (loadPackageFile contract incl. host-without-fs behavior)", - "examples/app-showcase/src/system/connectors/index.ts (showcase_status_openapi — the shipped happy-path fixture)" + "packages/services/service-automation/src/plugin.ts#createPackageFileLoader (createPackageFileLoader — all three exact error strings, #3016)", + "packages/spec/src/integration/connector.zod.ts#providerConfig (providerConfig.spec three-form doc) + connector-provider.ts (loadPackageFile contract incl. host-without-fs behavior)", + "examples/app-showcase/src/system/connectors/index.ts#showcase_status_openapi (showcase_status_openapi — the shipped happy-path fixture)" ], "history": [ { @@ -403,9 +403,9 @@ }, "source": [ "docs/plans/release-15.1-test-plan.md §B6 (#2985)", - "packages/services/service-automation/src/plugin.ts (auditDeclaredConnectors — exact warning text; boot + reload wiring)", + "packages/services/service-automation/src/plugin.ts#auditDeclaredConnectors (auditDeclaredConnectors — exact warning text; boot + reload wiring)", "packages/spec/src/integration/connector.zod.ts (enabled:false = deliberate catalog descriptor, #2612)", - "examples/app-showcase/src/system/connectors/index.ts (showcase_erp_catalog — the shipped quiet fixture)" + "examples/app-showcase/src/system/connectors/index.ts#showcase_erp_catalog (showcase_erp_catalog — the shipped quiet fixture)" ], "history": [ { @@ -480,7 +480,7 @@ ], "source": [ "docs/plans/release-15.1-test-plan.md §B2 (objectui#2563)", - "packages/services/service-automation/src/engine.ts (getConnectorDescriptors — the designer-facing shape incl. origin + actions[].inputSchema, ADR-0022)", + "packages/services/service-automation/src/engine.ts#getConnectorDescriptors (getConnectorDescriptors — the designer-facing shape incl. origin + actions[].inputSchema, ADR-0022)", "packages/runtime/src/route-ledger.ts (GET /automation/connectors)" ], "history": [ @@ -600,12 +600,12 @@ "ref": "packages/qa/dogfood/test/webhook-materialization.dogfood.test.ts" }, "source": [ - "packages/spec/src/automation/webhook.zod.ts (WebhookTriggerType enum + why undelete/api are absent; materialization contract; strict shape #4001)", + "packages/spec/src/automation/webhook.zod.ts#WebhookTriggerType (WebhookTriggerType enum + why undelete/api are absent; materialization contract; strict shape #4001)", "packages/spec/liveness/webhook.json (all 11 props live via the #3489 bridge; per-prop line refs)", "packages/plugins/plugin-webhooks/src/bootstrap-declared-webhooks.ts + auto-enqueuer.ts (remaps; trigger→event mapping incl. the #4639 bulk pair; #3196 unknown-trigger warn; seed-not-clobber)", - "packages/services/service-messaging/src/http-outbox.ts (delivery statuses, attempts; HttpRedeliverError + assertHttpRedeliverable/assertRedeliverAllowed — terminal-only, the attempts===0 parked-row refusal, the fail-closed guard) + packages/services/service-messaging/src/http-signature-at-rest.integration.test.ts (redelivery replays the stored signature byte-for-byte)", - "packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts (POST /api/v1/webhooks/redeliver — session auth with NO capability gate by design ('every authenticated user counts'), #10740 activeOrganizationId threading, the 401/400/404/409/500 arms) + (the #8069 redeliver-guard install; its ABSENCE is an error-level log, not a silent pass)", - "packages/rest/src/rest-server.ts (#3897 — the batch routes parse against the spec contract and Zod STRIPS unknown keys, so options.multi/options.where cannot ride in; deleteMany deletes per id. This is why the bulk clause must be driven from a flow node, not REST)", + "packages/services/service-messaging/src/http-outbox.ts#HttpRedeliverError (delivery statuses, attempts; HttpRedeliverError + assertHttpRedeliverable/assertRedeliverAllowed — terminal-only, the attempts===0 parked-row refusal, the fail-closed guard) + packages/services/service-messaging/src/http-signature-at-rest.integration.test.ts (redelivery replays the stored signature byte-for-byte)", + "packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts#session (POST /api/v1/webhooks/redeliver — session auth with NO capability gate by design ('every authenticated user counts'), #10740 activeOrganizationId threading, the 401/400/404/409/500 arms) + (the #8069 redeliver-guard install; its ABSENCE is an error-level log, not a silent pass)", + "packages/rest/src/rest-server.ts#deleteMany (#3897 — the batch routes parse against the spec contract and Zod STRIPS unknown keys, so options.multi/options.where cannot ride in; deleteMany deletes per id. This is why the bulk clause must be driven from a flow node, not REST)", "examples/app-showcase/src/automation/webhooks/index.ts (the shipped inactive fixture and its activation story)", "#3358 §9 (webhook undelete/api trigger removal gate)" ], @@ -727,11 +727,11 @@ "seed-data-thin" ], "source": [ - "packages/spec/src/system/job.zod.ts (ScheduleSchema discriminated union; JOB_ID_RETIRED; retryPolicy/timeout docs incl. the 17.0.0 maxRetries default flip #4661; JobExecutionStatus)", + "packages/spec/src/system/job.zod.ts#ScheduleSchema (ScheduleSchema discriminated union; JOB_ID_RETIRED; retryPolicy/timeout docs incl. the 17.0.0 maxRetries default flip #4661; JobExecutionStatus)", "packages/spec/liveness/job.json (per-prop verdicts + the #4509 closed-door rationale)", - "packages/runtime/src/app-plugin.ts (registration, enabled/handler skip lines)", + "packages/runtime/src/app-plugin.ts#handler (registration, enabled/handler skip lines)", "packages/services/service-job/src/cron-job-adapter.ts + db-job-adapter.ts (all three schedule shapes; sys_job/sys_job_run persistence) + run-with-policy.ts (retry/timeout enforcement, #3494)", - "examples/app-showcase/src/automation/jobs/index.ts (showcase_health_sweep fixture + its #4774/#4888 history)" + "examples/app-showcase/src/automation/jobs/index.ts#showcase_health_sweep (showcase_health_sweep fixture + its #4774/#4888 history)" ], "history": [ { @@ -852,14 +852,14 @@ "ref": "packages/qa/dogfood/test/email-template-materialization.dogfood.test.ts" }, "source": [ - "packages/spec/src/system/email-template.zod.ts (requireds, dotted-name regex, aliases #5013, variables/required, fromOverride/replyTo/active)", - "packages/spec/liveness/email_template.json (the whole surface live via #4509; per-prop evidence incl. requireVars, TEMPLATE_INACTIVE, seed-not-clobber, runtime re-materialization)", + "packages/spec/src/system/email-template.zod.ts#fromOverride (requireds, dotted-name regex, aliases #5013, variables/required, fromOverride/replyTo/active)", + "packages/spec/liveness/email_template.json#requireVars (the whole surface live via #4509; per-prop evidence incl. requireVars, TEMPLATE_INACTIVE, seed-not-clobber, runtime re-materialization)", "packages/plugins/plugin-email/src/bootstrap-declared-email-templates.ts + email-service.ts (row mapping; (name,locale)+fallback; the gates)", - "examples/app-showcase/src/system/emails/index.ts (showcase_task_done_email fixture)", - "packages/rest/src/rest-server.ts (registerEmailEndpoints — POST /api/v1/email/send: enforceAuth 401 UNAUTHENTICATED #3963, non-object→400 INVALID_REQUEST, VALIDATION_FAILED passthrough, 501 no-provider, 500 EMAIL_SEND_FAILED)", + "examples/app-showcase/src/system/emails/index.ts#showcase_task_done_email (showcase_task_done_email fixture)", + "packages/rest/src/rest-server.ts#registerEmailEndpoints (registerEmailEndpoints — POST /api/v1/email/send: enforceAuth 401 UNAUTHENTICATED #3963, non-object→400 INVALID_REQUEST, VALIDATION_FAILED passthrough, 501 no-provider, 500 EMAIL_SEND_FAILED)", "packages/rest/src/rest-route-ledger.ts (email family — POST /api/v1/email/send → client email.send)", - "packages/spec/src/api/error-code-ledger.zod.ts (EMAIL_SEND_FAILED under @objectstack/rest)", - "packages/spec/src/contracts/email-service.ts (SendEmailInput — the WIRE shape of POST /api/v1/email/send: to, subject, text?, html?; 'at least one of text or html must be supplied') + packages/plugins/plugin-email/src/email-service.ts (normalizeMessage — where the VALIDATION_FAILED texts come from)" + "packages/spec/src/api/error-code-ledger.zod.ts#EMAIL_SEND_FAILED (EMAIL_SEND_FAILED under @objectstack/rest)", + "packages/spec/src/contracts/email-service.ts#SendEmailInput (SendEmailInput — the WIRE shape of POST /api/v1/email/send: to, subject, text?, html?; 'at least one of text or html must be supplied') + packages/plugins/plugin-email/src/email-service.ts#normalizeMessage (normalizeMessage — where the VALIDATION_FAILED texts come from)" ], "history": [ { @@ -973,7 +973,7 @@ "packages/runtime/src/route-ledger.ts (GET /notifications, POST /notifications/read, POST /notifications/read/all)", "packages/spec/src/system/notification.zod.ts (channel enum + the #3197 implemented-vs-dead-letter note and 'in-app' vs 'inbox' drift)", "packages/services/service-messaging/src/inbox-channel.ts + channel.ts (inbox delivery writes; channels default ['inbox'])", - "examples/app-showcase/src/automation/flows/index.ts (showcase_task_assigned_notify — the worked ADR-0012 notify fixture)" + "examples/app-showcase/src/automation/flows/index.ts#showcase_task_assigned_notify (showcase_task_assigned_notify — the worked ADR-0012 notify fixture)" ], "history": [ { @@ -1164,13 +1164,13 @@ "ref": "packages/services/service-datasource/src/__tests__/admin-routes.test.ts (+ __tests__/envelope.conformance.test.ts) — pins route behavior + envelope; the LIVE-mount half is not pinned, drive os dev for it" }, "source": [ - "packages/services/service-datasource/src/admin-routes.ts (the eleven routes; the uniform floor docblock + requireDatasourceAdmin + DATASOURCE_ADMIN_CAPABILITY #9391/#9593; splitSecret keeps the secret out of the persisted draft; resolve()/badRequest() per-service attribution #4225/#4249; getDatasource credential-stripped + hasSecret; POST /test draft probe; POST /:name/migrate-credential)", - "packages/services/service-datasource/src/driver-catalog.ts (static DRIVER_CATALOG, configSchema projected from spec #4410)", - "packages/services/service-datasource/src/datasource-route-ledger.ts (DATASOURCE_ROUTE_LEDGER — the family's own audited ledger, #7744; all rows server-only, SDK question filed as #7954) + datasource-route-ledger.conformance.test.ts (both directions derived from the registrar)", - "packages/services/service-datasource/src/datasource-credential-migration.ts (CredentialMigrationPlan: bind / drop-inline / none / refuse with reason+remedy, #8155) + __tests__/datasource-credential-migration.test.ts (the plan pins)", - "packages/spec/src/api/error-code-ledger.zod.ts (DATASOURCE_ADMIN_ERROR, EXTERNAL_DATASOURCE_ERROR under @objectstack/service-datasource)", - "packages/spec/src/api/errors.zod.ts (HttpStatusErrorCodeMap: 503 SERVICE_UNAVAILABLE, 404 RESOURCE_NOT_FOUND)", - "packages/cli/src/commands/serve.ts (mounts registerDatasourceAdminRoutes at /api/v1/datasources by default — a raw IHttpServer mount, which is WHY the family carries its own ledger rather than a rest-route-ledger row)" + "packages/services/service-datasource/src/admin-routes.ts#requireDatasourceAdmin (the eleven routes; the uniform floor docblock + requireDatasourceAdmin + DATASOURCE_ADMIN_CAPABILITY #9391/#9593; splitSecret keeps the secret out of the persisted draft; resolve()/badRequest() per-service attribution #4225/#4249; getDatasource credential-stripped + hasSecret; POST /test draft probe; POST /:name/migrate-credential)", + "packages/services/service-datasource/src/driver-catalog.ts#DRIVER_CATALOG (static DRIVER_CATALOG, configSchema projected from spec #4410)", + "packages/services/service-datasource/src/datasource-route-ledger.ts#DATASOURCE_ROUTE_LEDGER (DATASOURCE_ROUTE_LEDGER — the family's own audited ledger, #7744; all rows server-only, SDK question filed as #7954) + datasource-route-ledger.conformance.test.ts (both directions derived from the registrar)", + "packages/services/service-datasource/src/datasource-credential-migration.ts#CredentialMigrationPlan (CredentialMigrationPlan: bind / drop-inline / none / refuse with reason+remedy, #8155) + __tests__/datasource-credential-migration.test.ts (the plan pins)", + "packages/spec/src/api/error-code-ledger.zod.ts#DATASOURCE_ADMIN_ERROR (DATASOURCE_ADMIN_ERROR, EXTERNAL_DATASOURCE_ERROR under @objectstack/service-datasource)", + "packages/spec/src/api/errors.zod.ts#HttpStatusErrorCodeMap (HttpStatusErrorCodeMap: 503 SERVICE_UNAVAILABLE, 404 RESOURCE_NOT_FOUND)", + "packages/cli/src/commands/serve.ts#registerDatasourceAdminRoutes (mounts registerDatasourceAdminRoutes at /api/v1/datasources by default — a raw IHttpServer mount, which is WHY the family carries its own ledger rather than a rest-route-ledger row)" ], "history": [ { @@ -1273,12 +1273,12 @@ "ref": "packages/rest/src/remote-tables-twin.equivalence.test.ts (#7955 request-shape + #9686/#9593 admission, driven through the real HonoHttpServer over the real ExternalDatasourceService) + packages/services/service-datasource/src/__tests__/admin-routes.test.ts — the LIVE-mount half (real showcase fixture, real sqlite introspection) is not pinned, drive os dev for it" }, "source": [ - "packages/services/service-datasource/src/admin-routes.ts (remote-tables + the #7955 coercion comment), (object-draft), + (requireDatasourceAdmin — 401 floor then manage_platform_settings 403, #9391/#9593)", - "packages/rest/src/external-datasource-routes.ts (the twin family GET /external/tables + POST.../draft), (refuseFederationRequest — the #9686 anonymous-deny floor, then the #9901/#10255 capability gate, both decided from one resolveExecutionContext call with anonymous first), (FEDERATION_READ_CAPABILITY = manage_platform_settings, what the two twin routes require) + (FEDERATION_WRITE_CAPABILITY = manage_metadata, the family's write half)", - "packages/services/service-datasource/src/external-datasource-service.ts (listRemoteTables schema filter + allowedSchemas), (generateObjectDraft), (renderObjectSource; the generated header)", - "packages/drivers/driver-sql/src/sql-driver.ts (introspectSchema; SQLite branch reads sqlite_master → bare unqualified table names)", + "packages/services/service-datasource/src/admin-routes.ts#requireDatasourceAdmin (remote-tables + the #7955 coercion comment), (object-draft), + (requireDatasourceAdmin — 401 floor then manage_platform_settings 403, #9391/#9593)", + "packages/rest/src/external-datasource-routes.ts#refuseFederationRequest (the twin family GET /external/tables + POST.../draft), (refuseFederationRequest — the #9686 anonymous-deny floor, then the #9901/#10255 capability gate, both decided from one resolveExecutionContext call with anonymous first), (FEDERATION_READ_CAPABILITY = manage_platform_settings, what the two twin routes require) + (FEDERATION_WRITE_CAPABILITY = manage_metadata, the family's write half)", + "packages/services/service-datasource/src/external-datasource-service.ts#listRemoteTables (listRemoteTables schema filter + allowedSchemas), (generateObjectDraft), (renderObjectSource; the generated header)", + "packages/drivers/driver-sql/src/sql-driver.ts#introspectSchema (introspectSchema; SQLite branch reads sqlite_master → bare unqualified table names)", "examples/app-showcase/src/system/datasources/ (showcase-external.datasource.ts + external-fixture.ts)", - "SURFACE NOTE (why api, not mixed): admin-routes.ts's comment names a Studio 'sync objects' consumer, but no such consumer was located — the only live callers found are the two CLI commands os datasource list-tables / os datasource introspect (packages/cli/src/commands/datasource/list-tables.ts, introspect.ts — both call the /external/tables spellings), covered by cli.datasource-introspect-codegen (authored in this same sweep). Same #9386/#9417 correction as external-datasource-federated-read rev 2" + "SURFACE NOTE (why api, not mixed): admin-routes.ts's comment names a Studio 'sync objects' consumer, but no such consumer was located — the only live callers found are the two CLI commands os datasource list-tables / os datasource introspect (packages/cli/src/commands/datasource/list-tables.ts#tables, introspect.ts — both call the /external/tables spellings), covered by cli.datasource-introspect-codegen (authored in this same sweep). Same #9386/#9417 correction as external-datasource-federated-read rev 2" ], "history": [ { @@ -1389,11 +1389,11 @@ "ref": "packages/runtime/src/external-validation-plugin.test.ts (fail-default / warn / ignore / default-when-unreadable + drift-event emission) + packages/runtime/src/external-validation-checkonboot.test.ts (#13037/#13149 — the checkOnBoot gate, its scope, and the one-spelling rule) + packages/runtime/src/list-diagnosed-consumer-sweep.test.ts (#6504 withheld all-clear) — the LIVE boot-abort arm is not pinned; an induced-drift boot is required for it" }, "source": [ - "packages/runtime/src/external-validation-plugin.ts (Gate 2 kernel:ready runValidation; the #13149 checkOnBoot gate + skip info line; the #11166 unreachable carve-out — warn and continue, only measured diffs reach onMismatch; the fail throw), (bootCheckEnabled — explicit false only, safe direction), (resolveOnMismatch — `?? 'fail'`), (memoized def loader; a failed read answers undefined → strict defaults), (announceAllClear #6504; the INCOMPLETE-sweep warn), (background drift checks + external.schema.drift events; the boot-step-only ruling ⛔)", - "packages/spec/src/shared/external-errors.ts (renderDiffMessage + ExternalSchemaMismatchError — datasource, object, per-column diffs)", - "packages/cli/src/commands/serve.ts (ADR-0015 federation block — ExternalDatasourceServicePlugin + createExternalValidationPlugin wired unconditionally, best-effort dynamic import)", - "packages/spec/src/data/datasource.zod.ts (validation policy schema; checkOnBoot default true, the checkonboot/validateonboot alias rows are REJECTION-path did-you-means, not folds — enforced since #13149, so declared = enforced here now)", - "examples/app-showcase/src/system/datasources/showcase-external.datasource.ts (stock policy onMismatch:'warn' — deliberate, per its own comment)", + "packages/runtime/src/external-validation-plugin.ts#runValidation (Gate 2 kernel:ready runValidation; the #13149 checkOnBoot gate + skip info line; the #11166 unreachable carve-out — warn and continue, only measured diffs reach onMismatch; the fail throw), (bootCheckEnabled — explicit false only, safe direction), (resolveOnMismatch — `?? 'fail'`), (memoized def loader; a failed read answers undefined → strict defaults), (announceAllClear #6504; the INCOMPLETE-sweep warn), (background drift checks + external.schema.drift events; the boot-step-only ruling ⛔)", + "packages/spec/src/shared/external-errors.ts#renderDiffMessage (renderDiffMessage + ExternalSchemaMismatchError — datasource, object, per-column diffs)", + "packages/cli/src/commands/serve.ts#ExternalDatasourceServicePlugin (ADR-0015 federation block — ExternalDatasourceServicePlugin + createExternalValidationPlugin wired unconditionally, best-effort dynamic import)", + "packages/spec/src/data/datasource.zod.ts#checkOnBoot (validation policy schema; checkOnBoot default true, the checkonboot/validateonboot alias rows are REJECTION-path did-you-means, not folds — enforced since #13149, so declared = enforced here now)", + "examples/app-showcase/src/system/datasources/showcase-external.datasource.ts#onMismatch (stock policy onMismatch:'warn' — deliberate, per its own comment)", "docs/adr/0015-external-datasource-federation.md §5.2" ], "history": [ @@ -1485,12 +1485,12 @@ "stale-dist" ], "source": [ - "packages/services/service-messaging/src/objects/notification-preference.object.ts (sys_notification_preference: user_id × topic × channel × enabled; '*' wildcards + admin-global default; unique (user_id,topic,channel) index)", - "packages/services/service-messaging/src/preference-resolver.ts (PreferenceResolver.filter — most-specific-wins, mandatory-topic bypass, fail-open; drops recipients left with no accepted channel)", - "packages/services/service-messaging/src/messaging-service.ts (emit() consults the PreferenceResolver before fan-out)", - "packages/services/service-messaging/src/messaging-service-plugin.ts (email/sms channels render sys_notification_template; shared retry/dead-letter substrate)", + "packages/services/service-messaging/src/objects/notification-preference.object.ts#user_id (sys_notification_preference: user_id × topic × channel × enabled; '*' wildcards + admin-global default; unique (user_id,topic,channel) index)", + "packages/services/service-messaging/src/preference-resolver.ts#PreferenceResolver (PreferenceResolver.filter — most-specific-wins, mandatory-topic bypass, fail-open; drops recipients left with no accepted channel)", + "packages/services/service-messaging/src/messaging-service.ts#PreferenceResolver (emit() consults the PreferenceResolver before fan-out)", + "packages/services/service-messaging/src/messaging-service-plugin.ts#sys_notification_template (email/sms channels render sys_notification_template; shared retry/dead-letter substrate)", "packages/spec/src/system/notification.zod.ts (#3197 dead-letter for unimplemented channels; 'in-app' vs 'inbox' naming drift)", - "examples/app-showcase/src/automation/flows/index.ts (showcase_task_assigned_notify — topic 'task.assigned', channels ['inbox'])" + "examples/app-showcase/src/automation/flows/index.ts#showcase_task_assigned_notify (showcase_task_assigned_notify — topic 'task.assigned', channels ['inbox'])" ], "history": [ { @@ -1596,7 +1596,7 @@ "ADR-0100 (credential read mask; §B3 echoed-mask write guard)", "ADR-0015 / ADR-0062 (credentials route through datasource config)", "packages/spec/src/data/secret-mask.ts (the one mask literal both readers import)", - "packages/spec/src/data/datasource-credential-redaction.ts (the ONE definition of 'what is a credential key' — schema-derived z.never() refusals, FORMER_CREDENTIAL_ALIASES, STILL_WRITABLE_CREDENTIAL_KEYS)", + "packages/spec/src/data/datasource-credential-redaction.ts#STILL_WRITABLE_CREDENTIAL_KEYS (the ONE definition of 'what is a credential key' — schema-derived z.never() refusals, FORMER_CREDENTIAL_ALIASES, STILL_WRITABLE_CREDENTIAL_KEYS)", "packages/spec/src/kernel/metadata-type-redaction.ts (the per-type redaction hook — the metadata read door)", "packages/services/service-datasource/src/datasource-config-redaction.ts + datasource-credential-migration.ts (the datasource-admin read door)", "packages/spec/src/migrations/entries/semantic/17.datasource-config-inline-credential-refused.ts · 17.datasource-config-placeholder-refused.ts · 17.datasource-config-url-userinfo-refused.ts · 17.connector-inline-authentication-publish-refused.ts", @@ -1696,11 +1696,11 @@ "ref": "packages/connectors/connector-rest/src/rest-connector.test.ts (per-kind wire shapes through a stubbed fetch) + packages/services/service-automation/src/connector-materialization.test.ts (credentialRef resolution incl. env fallback and the boot-fatal missing ref) — the LIVE echo-upstream leg is not pinned" }, "source": [ - "packages/spec/src/shared/connector-auth.zod.ts (ConnectorAuthConfigSchema — the runtime shape; ResolvedConnectorAuth = the static open tier, OAuth2 named as the enterprise tier), (ConnectorInstanceAuthSchema — the authored shape: credentialRef instead of inline secrets, four arms, no oauth2)", - "packages/spec/src/integration/connector.zod.ts (the #7990 inline-`authentication` publish refusal, both message variants)", - "packages/services/service-automation/src/plugin.ts (resolveInstanceAuth — credentialRef → ResolvedConnectorAuth; empty resolution throws with the env-var prescription; api-key headerName default applied)", - "packages/connectors/connector-rest/src/rest-connector.ts (applyAuth — the wire shapes per kind) + rest-provider.ts (the factory receives the RESOLVED static subset)", - "packages/connectors/connector-openapi/src/openapi-provider.ts (the openapi provider shares the same static-auth transport)", + "packages/spec/src/shared/connector-auth.zod.ts#ConnectorAuthConfigSchema (ConnectorAuthConfigSchema — the runtime shape; ResolvedConnectorAuth = the static open tier, OAuth2 named as the enterprise tier), (ConnectorInstanceAuthSchema — the authored shape: credentialRef instead of inline secrets, four arms, no oauth2)", + "packages/spec/src/integration/connector.zod.ts#authentication (the #7990 inline-`authentication` publish refusal, both message variants)", + "packages/services/service-automation/src/plugin.ts#resolveInstanceAuth (resolveInstanceAuth — credentialRef → ResolvedConnectorAuth; empty resolution throws with the env-var prescription; api-key headerName default applied)", + "packages/connectors/connector-rest/src/rest-connector.ts#applyAuth (applyAuth — the wire shapes per kind) + rest-provider.ts (the factory receives the RESOLVED static subset)", + "packages/connectors/connector-openapi/src/openapi-provider.ts#auth (the openapi provider shares the same static-auth transport)", "examples/app-showcase/src/system/connectors/index.ts,66,103,142 (all shipped instances auth none; the fixture's own pointer at bearer+credentialRef for real upstreams)" ], "history": [ @@ -1809,8 +1809,8 @@ "objectui packages/app-shell/src/views/metadata-admin/external/api.ts (the five-route client; the retired-shape 503 detector; listRemoteTables/generateObjectDraft/refreshCatalog/validateDatasource/importObjectDraft — the import door is PUT /api/v1/meta/object/:name)", "objectui packages/app-shell/src/views/metadata-admin/external/{SchemaBrowser,ImportObjectDialog,ValidationPanel,ExternalDatasourcePanel}.tsx (the P5 surfaces; ValidationPanel.tsx DIFF_LABEL total over the imported spec union — the #4115 recurrence guard) + previews/DatasourcePreview.tsx (panel keyed off schemaMode !== 'managed')", "objectui packages/app-shell/src/views/metadata-admin/datasource/register.ts (Setup → Datasources = the engine route; left-nav names it directly, objectui#3660)", - "packages/rest/src/external-datasource-routes.ts (the five-route family), (import → 201 { object }, EXTERNAL_IMPORT_ERROR), (refresh-catalog), (validate, #10537 URL-scoped), (refuseFederationRequest — read=manage_platform_settings, write=manage_metadata, #9686 anonymous floor), (the 503 envelope)", - "packages/spec/src/shared/external-errors.ts (SchemaDiffEntryKind — 10 kinds at head incl. index_mismatch/unmapped_index/default_mismatch/unreachable)", + "packages/rest/src/external-datasource-routes.ts#refuseFederationRequest (the five-route family), (import → 201 { object }, EXTERNAL_IMPORT_ERROR), (refresh-catalog), (validate, #10537 URL-scoped), (refuseFederationRequest — read=manage_platform_settings, write=manage_metadata, #9686 anonymous floor), (the 503 envelope)", + "packages/spec/src/shared/external-errors.ts#SchemaDiffEntryKind (SchemaDiffEntryKind — 10 kinds at head incl. index_mismatch/unmapped_index/default_mismatch/unreachable)", "packages/services/service-datasource/src/external-datasource-service.ts (the 'unreachable' producer)", "sibling coverage this item deliberately does NOT duplicate: external-schema-introspection (tables/draft API contract + twin equivalence + auth-floor pins), external-datasource-federated-read (the query path), external-schema-drift-gate (boot gate; its induced-drift recipe notes are reused here for diff-label evidence)" ], diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 0f349bd703..3ef2375469 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -283,7 +283,7 @@ "automated": { "kind": "e2e", "ref": "examples/app-showcase/e2e/showcase-smoke.spec.ts" }, "traps": ["hydration-race", "single-datapoint", "stale-console-bundle"], "source": [ - "examples/app-showcase/e2e/showcase-smoke.spec.ts (SURFACES)", + "examples/app-showcase/e2e/showcase-smoke.spec.ts#SURFACES (SURFACES)", ".github/workflows/showcase-smoke.yml", "objectui: packages/app-shell/src/chrome/ErrorBoundary.tsx (route-level fallback + recovery actions)", "examples/app-showcase/src/ui/apps/index.ts (nav truth)" @@ -367,10 +367,10 @@ "traps": ["dispatcher-vs-hono-route"], "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/meta-types-create-seed.dogfood.test.ts" }, "source": [ - "packages/spec/src/kernel/metadata-plugin.zod.ts (MetadataTypeSchema + DEFAULT_METADATA_TYPE_REGISTRY)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#MetadataTypeSchema (MetadataTypeSchema + DEFAULT_METADATA_TYPE_REGISTRY)", "packages/spec/src/kernel/metadata-create-seeds.ts", "packages/runtime/src/route-ledger.ts (GET /meta, GET /meta/types, GET /meta/:type, GET /meta/:type/:name)", - "examples/app-showcase/src/data/extensions/account.extension.ts (AccountExtension — additive overlay on showcase_account, priority 210)", + "examples/app-showcase/src/data/extensions/account.extension.ts#AccountExtension (AccountExtension — additive overlay on showcase_account, priority 210)", "packages/spec/src/data (defineObjectExtension — extend merges fields at registerApp, higher priority wins on conflict)" ], "history": [ @@ -462,8 +462,8 @@ }, "source": [ "packages/runtime/src/route-ledger.ts (PUT /meta/:type/:name, GET /meta/_drafts, GET /meta/:type/:name/published, POST /packages/:id/publish-drafts)", - "packages/spec/src/kernel/metadata-plugin.zod.ts (allowOrgOverride 403 not_overridable contract; validateOnWrite; registry flags per type)", - "ADR-0070 via packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts (writable_package_required)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#allowOrgOverride (allowOrgOverride 403 not_overridable contract; validateOnWrite; registry flags per type)", + "ADR-0070 via packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts#writable_package_required (writable_package_required)", "ADR-0033 (drafts / publish)" ], "history": [ @@ -557,11 +557,11 @@ ], "traps": ["hydration-race", "wrong-persona", "stale-console-bundle"], "source": [ - "packages/platform-objects/src/apps/setup.app.ts (shell group anchors + requiredPermissions setup.access)", - "packages/platform-objects/src/apps/studio.app.ts (static nav; requiredPermissions studio.access)", - "packages/platform-objects/src/apps/account.app.ts (no requiredPermissions; hidden from App Switcher; requiresObject/requiresService entry gates)", - "packages/platform-objects/src/apps/setup-nav.contributions.ts (nav_organizations requiresService org-scoping; nav_api_keys manage_platform_settings; no nav_jwks — removed in #7544)", - "packages/plugins/plugin-auth/src/auth-plugin.ts (SSO Providers nav_sso_providers contributed only when isSsoWired())", + "packages/platform-objects/src/apps/setup.app.ts#requiredPermissions (shell group anchors + requiredPermissions setup.access)", + "packages/platform-objects/src/apps/studio.app.ts#requiredPermissions (static nav; requiredPermissions studio.access)", + "packages/platform-objects/src/apps/account.app.ts#requiresObject (no requiredPermissions; hidden from App Switcher; requiresObject/requiresService entry gates)", + "packages/platform-objects/src/apps/setup-nav.contributions.ts#nav_organizations (nav_organizations requiresService org-scoping; nav_api_keys manage_platform_settings; no nav_jwks — removed in #7544)", + "packages/plugins/plugin-auth/src/auth-plugin.ts#Providers (SSO Providers nav_sso_providers contributed only when isSsoWired())", "ADR-0048 (Setup/Studio/Account as one-app packages com.objectstack.{setup,studio,account}); ADR-0029 (nav contributions); ADR-0066 (sys_jwks private)" ], "history": [ @@ -717,13 +717,13 @@ ], "traps": ["stale-console-bundle", "dispatcher-vs-hono-route"], "source": [ - "packages/services/service-settings/src/settings-routes.ts (GET/PUT /api/settings, POST :ns/:actionId; 403 SETTINGS_FORBIDDEN / 409 SETTINGS_LOCKED / 400 SETTINGS_VALIDATION|UNKNOWN_KEY mapping; secure anonymous default)", - "packages/services/service-settings/src/settings-service.ts (cascade source default→scope; effectiveEnvOverride lock; encrypted→sys_secret handle via cryptoProvider+secretStore; validatePatch invalid_option)", - "packages/services/service-settings/src/settings-service-plugin.ts (verifiedContextFromRequest fail-closed; buildAuditWriter→sys_setting_audit; buildSecretStore→sys_secret; LocalCryptoProvider)", - "packages/services/service-settings/src/settings-service.types.ts (envKeyOf OS__)", + "packages/services/service-settings/src/settings-routes.ts#actionId (GET/PUT /api/settings, POST :ns/:actionId; 403 SETTINGS_FORBIDDEN / 409 SETTINGS_LOCKED / 400 SETTINGS_VALIDATION|UNKNOWN_KEY mapping; secure anonymous default)", + "packages/services/service-settings/src/settings-service.ts#effectiveEnvOverride (cascade source default→scope; effectiveEnvOverride lock; encrypted→sys_secret handle via cryptoProvider+secretStore; validatePatch invalid_option)", + "packages/services/service-settings/src/settings-service-plugin.ts#verifiedContextFromRequest (verifiedContextFromRequest fail-closed; buildAuditWriter→sys_setting_audit; buildSecretStore→sys_secret; LocalCryptoProvider)", + "packages/services/service-settings/src/settings-service.types.ts#envKeyOf (envKeyOf OS__)", "packages/services/service-settings/src/manifests/{localization,mail,ai,branding}.manifest.ts", - "packages/core/src/security/resolve-authz-context.ts (localization timezone/locale/currency → ExecutionContext — the observable consumer)", - "packages/services/service-settings/src/settings-secret-redaction.ts (SETTINGS_SECRET_MASK = eight U+2022 BULLETs; READ: set→mask, unset→null; WRITE: a submitted value equal to the mask means 'unchanged' and the key is DROPPED from the patch)", + "packages/core/src/security/resolve-authz-context.ts#locale (localization timezone/locale/currency → ExecutionContext — the observable consumer)", + "packages/services/service-settings/src/settings-secret-redaction.ts#SETTINGS_SECRET_MASK (SETTINGS_SECRET_MASK = eight U+2022 BULLETs; READ: set→mask, unset→null; WRITE: a submitted value equal to the mask means 'unchanged' and the key is DROPPED from the patch)", "packages/services/service-settings/src/settings-routes.test.ts (#7522 / PR #7554 — the ten route-level pins these secret clauses mirror rather than re-inventing)", "framework#5087 (a test action must not fake success), #5204 (env override enforcement)" ], @@ -819,7 +819,7 @@ ], "traps": ["dispatcher-vs-hono-route", "stale-console-bundle"], "source": [ - "packages/runtime/src/route-ledger.ts (PATCH /packages/:id/enable|disable, DELETE /packages/:id, POST /packages/:id/publish|publish-drafts, GET /packages/:id/commits, POST /packages/:id/commits/:commitId/revert, POST /packages/:id/revert)", + "packages/runtime/src/route-ledger.ts#commitId (PATCH /packages/:id/enable|disable, DELETE /packages/:id, POST /packages/:id/publish|publish-drafts, GET /packages/:id/commits, POST /packages/:id/commits/:commitId/revert, POST /packages/:id/revert)", "platform-core.metadata-authoring-roundtrip (the writable probe package this reuses)", "ADR-0070 (writable vs read-only packages), ADR-0033 (drafts/publish/commits)" ], @@ -913,7 +913,7 @@ "objectui packages/app-shell/src/layout/inboxGrouping.ts (groupNotifications — (topic,title) coalescing, #2765)", "objectui packages/app-shell/src/layout/AppHeader.tsx (postMarkRead → POST /api/v1/notifications/read[/all]; poll GET /notifications?view=mine)", "packages/runtime/src/route-ledger.ts (GET /notifications, POST /notifications/read, POST /notifications/read/all)", - "packages/runtime/src/domains/notifications.ts (markRead upserts sys_notification_receipt; empty/mis-keyed ids → 400)", + "packages/runtime/src/domains/notifications.ts#markRead (markRead upserts sys_notification_receipt; empty/mis-keyed ids → 400)", "ADR-0030 (sys_inbox_message L5 materialization + sys_notification_receipt); ADR-0103 (receipt object api-method lockdown)" ], "history": [ @@ -1177,8 +1177,8 @@ "ADR-0046 §6 (documentation spine), §6.2.1 (membership is DERIVED), §6.4 (implicit per-package book), §6.7 (read-layer audience gating)", "ADR-0090 (audience vocabulary)", "ADR-0049 (fail closed — unresolvable holdings deny)", - "packages/spec/src/system/book.zod.ts — audienceAllows / resolveDocAudiences / docAudienceAllows / resolveBookClaimedDocs / deriveImplicitPackageBook / resolveBookTree (UNCATEGORIZED_KEY exclusion)", - "packages/rest/src/rest-server.ts — GET /book/:name/tree (audience gate, per-entry filter, resolveDocLocale, refuseRepeatedQueryParams)", + "packages/spec/src/system/book.zod.ts#audienceAllows — audienceAllows / resolveDocAudiences / docAudienceAllows / resolveBookClaimedDocs / deriveImplicitPackageBook / resolveBookTree (UNCATEGORIZED_KEY exclusion)", + "packages/rest/src/rest-server.ts#metaPath — GET /book/:name/tree (audience gate, per-entry filter, resolveDocLocale, refuseRepeatedQueryParams)", "#2774 (feat(security)!: enforce book audience at the REST read layer)", "packages/spec/liveness/book.json + doc.json (the ledgers whose coverage.json waivers this item retires)" ], @@ -1260,10 +1260,10 @@ "traps": ["absence-inference", "stale-dist"], "automated": { "kind": "unit", "ref": "packages/runtime/src/migration-recovery-plugin.test.ts" }, "source": [ - "packages/runtime/src/migration-recovery-plugin.ts — kernel:ready hook; silent no-engine return; silent absent-journal return; scan-failure warning (string); per-run warns + NOT-resumed summary; describeInterruptedRun (unresumable variant); the boot-discovers/CLI-acts ruling in the header", - "packages/core/src/utils/migration-journal.ts — findInterruptedRuns (started ∧ ¬done; run_done concludes; a run_failed run fully compensated is settled)", - "packages/platform-objects/src/system/sys-migration-journal.object.ts (row contract, (run_id, seq) unique index, apiMethods get/list — registered by PlatformObjectsPlugin)", - "packages/cli/src/commands/serve.ts (PlatformObjectsPlugin auto-registered into every served kernel — which is why the absent-journal arm cannot be staged on a served boot; MigrationRecoveryPlugin itself is auto-registered NOWHERE)", + "packages/runtime/src/migration-recovery-plugin.ts#describeInterruptedRun — kernel:ready hook; silent no-engine return; silent absent-journal return; scan-failure warning (string); per-run warns + NOT-resumed summary; describeInterruptedRun (unresumable variant); the boot-discovers/CLI-acts ruling in the header", + "packages/core/src/utils/migration-journal.ts#findInterruptedRuns — findInterruptedRuns (started ∧ ¬done; run_done concludes; a run_failed run fully compensated is settled)", + "packages/platform-objects/src/system/sys-migration-journal.object.ts#run_id (row contract, (run_id, seq) unique index, apiMethods get/list — registered by PlatformObjectsPlugin)", + "packages/cli/src/commands/serve.ts#kernel (PlatformObjectsPlugin auto-registered into every served kernel — which is why the absent-journal arm cannot be staged on a served boot; MigrationRecoveryPlugin itself is auto-registered NOWHERE)", "packages/runtime/CHANGELOG.md 17.0.0, commit 071d0dc — 'boot reconciliation and os migrate resume for the migration journal' (ADR-0119 D2, #4617 deliverable 3)", "cli.migrate-plan-apply-json (the CLI side of the same journal — this item is the boot-side DETECTION that item's `os migrate resume --json` step presumes)" ], @@ -1364,9 +1364,9 @@ "eventual-consistency" ], "source": [ - "packages/metadata-core/src/protocol-handshake.ts — resolveDeclaredRange (protocol → platform → legacy engine.objectstack); rangeAdmitsMajor (null = unrecognized, admit-with-warning); checkProtocolCompat (the OS_PROTOCOL_INCOMPATIBLE diagnostic and its migrateCommand); assertProtocolCompat (the four arms: ok silent, no-range warn, unparsed-range warn, incompatible throw)", - "packages/metadata-protocol/src/protocol.ts — namespace derivation for namespace-less runtime packages, then assertProtocolCompat(manifest) BEFORE registry.installPackage", - "packages/objectql/src/registry.ts — the ADR-0048 install-time namespace gate (shareable exemption, same-package exclusion, OS_METADATA_COLLISION=warn downgrade); NamespaceConflictError", + "packages/metadata-core/src/protocol-handshake.ts#resolveDeclaredRange — resolveDeclaredRange (protocol → platform → legacy engine.objectstack); rangeAdmitsMajor (null = unrecognized, admit-with-warning); checkProtocolCompat (the OS_PROTOCOL_INCOMPATIBLE diagnostic and its migrateCommand); assertProtocolCompat (the four arms: ok silent, no-range warn, unparsed-range warn, incompatible throw)", + "packages/metadata-protocol/src/protocol.ts#assertProtocolCompat — namespace derivation for namespace-less runtime packages, then assertProtocolCompat(manifest) BEFORE registry.installPackage", + "packages/objectql/src/registry.ts#NamespaceConflictError — the ADR-0048 install-time namespace gate (shareable exemption, same-package exclusion, OS_METADATA_COLLISION=warn downgrade); NamespaceConflictError", "ADR-0087 (metadata protocol upgrade contract) D1 · ADR-0048 (cross-package metadata collision) · ADR-0025 §3.10 #3 (protocol-first precedence)", "content/docs/releases/v15.mdx — 'ADR-0087 completed (#2972)': the handshake on boot-time durable rehydration and on code-defined AppPlugin loads, plus the objectstack lint nudge", "sibling items: api-backend.package-rest-lifecycle (the same install door, judged on its 201/409/PATCH contract rather than on the manifest gates) · cli.plugin-manifest-build-contract (the same manifest at the packaging boundary) · platform-core.package-lifecycle-enable-disable (what happens to a package after it installs)" @@ -1451,10 +1451,10 @@ "ref": "packages/qa/dogfood/test/packaged-activation-ledger-reach.dogfood.test.ts (both describes); packages/services/service-automation/src/activation-ledger-registration.test.ts (the negative half of the MOVE); packages/platform-objects/src/plugin.test.ts (the owning manifest + routing triple)" }, "source": [ - "packages/platform-objects/src/plugin.ts (the registration-home rationale — the measured 503; MOVE-not-add), (why the ledger rides its OWN manifest — the datasource-routing measurement), (ACTIVATION_LEDGER_MANIFEST), (the two register calls), (lean-kernel degradation: no manifest service → the door refuses loudly with 503 rather than keeping a bit that reverts)", - "packages/services/service-automation/src/plugin.ts (the flow leg attaches by probe() of the real table — runObjectRegistered no longer vouches for it; not attached on a failed probe)", - "packages/spec/src/system/constants/platform-object-names.ts (PLATFORM_OBJECTS_BY_PACKAGE receipt)", - "packages/verify/src/harness.ts (bootStack's automation option — how the no-automation composition is constructed)", + "packages/platform-objects/src/plugin.ts#ACTIVATION_LEDGER_MANIFEST (the registration-home rationale — the measured 503; MOVE-not-add), (why the ledger rides its OWN manifest — the datasource-routing measurement), (ACTIVATION_LEDGER_MANIFEST), (the two register calls), (lean-kernel degradation: no manifest service → the door refuses loudly with 503 rather than keeping a bit that reverts)", + "packages/services/service-automation/src/plugin.ts#runObjectRegistered (the flow leg attaches by probe() of the real table — runObjectRegistered no longer vouches for it; not attached on a failed probe)", + "packages/spec/src/system/constants/platform-object-names.ts#PLATFORM_OBJECTS_BY_PACKAGE (PLATFORM_OBJECTS_BY_PACKAGE receipt)", + "packages/verify/src/harness.ts#bootStack (bootStack's automation option — how the no-automation composition is constructed)", "docs/adr/0126-packaged-metadata-customization-model.md §4 (one generic activation ledger)", "#12438 (the scoped sweep this item lands from), #12419 (the registration-home PR), #12359 (the 503 measurement + the 2026-08-26 「同意」 ruling: registration follows the declaration)" ], @@ -1552,11 +1552,11 @@ "ref": "packages/objectql/src/action-activation.test.ts + packages/services/service-automation/src/flow-activation-ledger.test.ts (the store-level pins: org-row skip, 0-reads-as-false, update-not-delete, organization_id never in the write payload); packages/qa/dogfood/test/packaged-activation-ledger-reach.dogfood.test.ts (row shape + update-not-delete on a live boot)" }, "source": [ - "packages/platform-objects/src/system/sys-metadata-activation.object.ts — apiMethods ['get','list']; unique:'organization' NULL-collapsed index + the #5030 rationale; organization_id reserved-NULL; no-lifecycle ruling; what the object is NOT (§4 posture)", - "packages/core/src/utils/metadata-activation-store.ts — org-row skip on read (the wall); read-then-write setActive, organization_id never in the payload; delete-less engine slice; 0-reads-as-false", - "packages/runtime/src/api-exposure.ts (checkApiExposure — the 405 shape for a whitelisted-methods object)", + "packages/platform-objects/src/system/sys-metadata-activation.object.ts#apiMethods — apiMethods ['get','list']; unique:'organization' NULL-collapsed index + the #5030 rationale; organization_id reserved-NULL; no-lifecycle ruling; what the object is NOT (§4 posture)", + "packages/core/src/utils/metadata-activation-store.ts#setActive — org-row skip on read (the wall); read-then-write setActive, organization_id never in the payload; delete-less engine slice; 0-reads-as-false", + "packages/runtime/src/api-exposure.ts#checkApiExposure (checkApiExposure — the 405 shape for a whitelisted-methods object)", "packages/services/service-automation/src/flow-activation-store.ts + packages/objectql/src/action-activation.ts (the two metadata_type string literals — the whole live vocabulary)", - "packages/objectql/src/engine.ts (listDisabledActions — zero route consumers; the data-API read is the only operability surface)", + "packages/objectql/src/engine.ts#listDisabledActions (listDisabledActions — zero route consumers; the data-API read is the only operability surface)", "docs/adr/0126-packaged-metadata-customization-model.md §3 (scope wall + amendment ruling 1), §4 (row contract), §5 (org column reserved), §6 wall 3 (record the choice, never erase it)", "#12438 (the scoped sweep), #12419 (registration home — the sibling item), #12159 (the permission-rides-its-own-field ruling, 2026-08-26)" ], @@ -1623,10 +1623,10 @@ ], "traps": ["dispatcher-vs-hono-route", "cache-staleness"], "source": [ - "examples/app-showcase/src/data/extensions/account.extension.ts (AccountExtension — the shipped fixture, priority 210)", - "packages/objectql/src/registry.ts (mergeObjectDefinitions — additive fields fold, the spread) + vicinity via engine: packages/objectql/src/engine.ts (the objectExtensions loop registering 'extend' contributors)", - "packages/spec/src/data/object.zod.ts (ObjectExtensionSchema — what an extension may carry, the #4001 silently-dropped history, 'Fields to add/override', priority wins-on-conflict; defineObjectExtension)", - "packages/metadata-protocol/src/protocol.ts (the NOT_OVERRIDABLE refusal text and escape hatch)", + "examples/app-showcase/src/data/extensions/account.extension.ts#AccountExtension (AccountExtension — the shipped fixture, priority 210)", + "packages/objectql/src/registry.ts#mergeObjectDefinitions (mergeObjectDefinitions — additive fields fold, the spread) + vicinity via engine: packages/objectql/src/engine.ts#objectExtensions (the objectExtensions loop registering 'extend' contributors)", + "packages/spec/src/data/object.zod.ts#ObjectExtensionSchema (ObjectExtensionSchema — what an extension may carry, the #4001 silently-dropped history, 'Fields to add/override', priority wins-on-conflict; defineObjectExtension)", + "packages/metadata-protocol/src/protocol.ts#refusal (the NOT_OVERRIDABLE refusal text and escape hatch)", "packages/plugins/plugin-auth/src/managed-extension-fields.ts + managed-extension-fields.test.ts (the build-time collision gate — better-auth-managed objects ONLY; adopted by ADR-0126 §3 as Regime E prior art, NOT a generic gate)", "docs/adr/0126-packaged-metadata-customization-model.md §2-§3 (Regime E: additive contributions at package grain, base never edited, install-grain not per-org)", "content/docs/capabilities/integrations.mdx + content/docs/build-without-code.mdx (the E1 docs claim this item grounds)", @@ -1734,10 +1734,10 @@ ], "traps": ["dispatcher-vs-hono-route", "absence-inference", "destructive-in-place", "wrong-persona", "auth-state-leak"], "source": [ - "packages/cli/src/commands/serve.ts (planMarketplaceWiring — the pure wiring decision; cloud arm vs offline arm, host-composed instances never replaced), (INSTALL_LOCAL/RUNTIME_CONFIG/MARKETPLACE_PROXY/CLOUD_CONNECTION identities + RUNTIME_CONFIG_OPTIONS + OFFLINE_CONTROL_PLANE and why '' vs 'off' must differ), (the §5 wiring block: #8343 install-local deliberately NOT gated on a cloud URL; #8389 offline runtime-config mount)", - "packages/cloud-connection/src/marketplace-install-local-plugin.ts (header contract; INSTALL_LOCAL_CAPABILITY = manage_metadata with the #8976 rationale; reseed/purge mounts; purge semantics; rehydrate skip on sampleDataPurged)", + "packages/cli/src/commands/serve.ts#planMarketplaceWiring (planMarketplaceWiring — the pure wiring decision; cloud arm vs offline arm, host-composed instances never replaced), (INSTALL_LOCAL/RUNTIME_CONFIG/MARKETPLACE_PROXY/CLOUD_CONNECTION identities + RUNTIME_CONFIG_OPTIONS + OFFLINE_CONTROL_PLANE and why '' vs 'off' must differ), (the §5 wiring block: #8343 install-local deliberately NOT gated on a cloud URL; #8389 offline runtime-config mount)", + "packages/cloud-connection/src/marketplace-install-local-plugin.ts#INSTALL_LOCAL_CAPABILITY (header contract; INSTALL_LOCAL_CAPABILITY = manage_metadata with the #8976 rationale; reseed/purge mounts; purge semantics; rehydrate skip on sampleDataPurged)", "packages/cloud-connection/src/cloud-connection-route-ledger.ts (the five install-local rows: POST/GET/DELETE + reseed-sample-data + purge-sample-data, each with its gate stated), the marketplace-proxy passthrough row, and the runtime-config rows", - "packages/cli/src/commands/package/install.ts (inline artifact mode), (the POST to /api/v1/marketplace/install-local), (the 401 remedy)", + "packages/cli/src/commands/package/install.ts#artifact (inline artifact mode), (the POST to /api/v1/marketplace/install-local), (the 401 remedy)", "#8343 (install-local off the cloud gate), #8389 (offline runtime-config discovery), #8356 (features.marketplace derived), #8388 (features.installLocal derived), #8976 (manage_metadata gate), #9011 (per-principal projection)" ], "history": [ @@ -1820,8 +1820,8 @@ "traps": ["stale-console-bundle", "hydration-race", "absence-inference", "wrong-persona"], "source": [ "objectui packages/app-shell/src/console/marketplace/MarketplaceDisabled.tsx (the configuration-conclusion card, #5504 — its header states the whole design rationale), MarketplaceAccessDenied.tsx + MarketplacePackagePage.tsx and MarketplacePage.tsx (guard/disabled resolution order, #5583), MarketplaceResolving.tsx (the no-flash window, #5533/#5557), marketplaceApi.ts (isMarketplaceEnabled reads the server-pushed runtime config, never infers from failure)", - "packages/cloud-connection/src/marketplace-ui.ts (MARKETPLACE_BROWSE_UI_BUNDLE → nav_marketplace_browse owned by the proxy; MARKETPLACE_INSTALLED_UI_BUNDLE → nav_marketplace_installed + the marketplace_installed page owned by install-local)", - "packages/cloud-connection/src/cloud-connection-ui.ts (CLOUD_CONNECTION_NAV_CONTRIBUTIONS → nav_cloud_connection)", + "packages/cloud-connection/src/marketplace-ui.ts#MARKETPLACE_BROWSE_UI_BUNDLE (MARKETPLACE_BROWSE_UI_BUNDLE → nav_marketplace_browse owned by the proxy; MARKETPLACE_INSTALLED_UI_BUNDLE → nav_marketplace_installed + the marketplace_installed page owned by install-local)", + "packages/cloud-connection/src/cloud-connection-ui.ts#CLOUD_CONNECTION_NAV_CONTRIBUTIONS (CLOUD_CONNECTION_NAV_CONTRIBUTIONS → nav_cloud_connection)", "objectui apps/console/src/AppContent.tsx (marketplace routes registered by DefaultAppContent for every host)", "platform-core.marketplace-install-local-lifecycle (the api-lane sibling this item's wire captures delegate their effects to)" ], @@ -1891,9 +1891,9 @@ ], "traps": ["dispatcher-vs-hono-route", "absence-inference", "cache-staleness"], "source": [ - "packages/cloud-connection/src/runtime-config-plugin.ts (the payload contract in the header; features.marketplace derived from the live route table #8356+; features.installLocal derived with the option as ceiling #8388+; branding key resolution; telemetry.errorReporting #12681)", + "packages/cloud-connection/src/runtime-config-plugin.ts#installLocal (the payload contract in the header; features.marketplace derived from the live route table #8356+; features.installLocal derived with the option as ceiling #8388+; branding key resolution; telemetry.errorReporting #12681)", "packages/cloud-connection/src/cloud-connection-route-ledger.ts (both runtime-config rows: the anonymous-by-construction posture and why the alias is its own row)", - "packages/cli/src/commands/serve.ts (RuntimeConfigPlugin mounted on the cloud arm AND the offline arm — #8389; RUNTIME_CONFIG_OPTIONS shared by both on purpose so the arms differ only in what is mounted)", + "packages/cli/src/commands/serve.ts#RUNTIME_CONFIG_OPTIONS (RuntimeConfigPlugin mounted on the cloud arm AND the offline arm — #8389; RUNTIME_CONFIG_OPTIONS shared by both on purpose so the arms differ only in what is mounted)", "platform-core.marketplace-install-local-lifecycle (owns the flag-vs-route-table agreement; this item owns the route/alias/payload-hygiene contract)", "api-backend.route-ledger-live-parity (samples GET /api/v1/runtime/config only for raw-app MOUNT parity and defers the marketplace/runtime-config depth to platform-core by its own words — the split is deliberate on both sides)" ], @@ -2163,7 +2163,7 @@ "objectui packages/app-shell/src/context/CommandPaletteProvider.tsx (⌘/Ctrl+K — wired)", "objectui packages/components/src/ui/sidebar.tsx (SIDEBAR_KEYBOARD_SHORTCUT = 'b'), (the handler requires metaKey||ctrlKey — the source of the B-vs-⌘B display mismatch)", "objectui packages/app-shell/src/layout/chatDockState.ts (matchChatDockShortcut — ⌘/Ctrl+Shift+I, composer-safe)", - "objectui packages/app-shell/src/console/ai/AiChatPage.tsx:~605-615 (matchAiChatShortcut — ⌘⇧O/⌘⇧S, PAGE-scoped)", + "objectui packages/app-shell/src/console/ai/AiChatPage.tsx (matchAiChatShortcut — ⌘⇧O/⌘⇧S, PAGE-scoped)", "repo-grep evidence for the five dead accelerators: no handler for ⌘/ / N / R / ⌘E / ⌘D anywhere in app-shell, console or components (the only near-misses are page-scoped: ApprovalsInboxPage's plain `r` quick-decide and useDebugMode's Ctrl+Shift+D — neither matches an advertised accelerator)" ], "history": [ @@ -2354,11 +2354,11 @@ "ref": "packages/objectql/src/lifecycle/lifecycle-service.test.ts (archive-pending skip, bounded reap, rotation fallback, governance)" }, "source": [ - "packages/objectql/src/lifecycle/lifecycle-service.ts (the ADR-0057 enforcer: header contract — reaper/rotator/archiver, the never-hot-delete-unarchived safety rule, #5194 bounds; SYSTEM_CTX; DEFAULT_LIFECYCLE_SWEEP_MS 3_600_000; DEFAULT_LIFECYCLE_INITIAL_DELAY_MS 60_000; REAP_BATCH_SIZE 500 / REAP_MAX_BATCHES_PER_SWEEP 20; enabled getter incl. OS_LIFECYCLE_DISABLED; start()/timer; archive safety; the aggregate sweep line)", - "packages/objectql/src/plugin.ts (registered as service 'lifecycle' by the engine plugin — every kernel with data has enforcement; a declared retention driving no sweeper is ADR-0049 dead surface), (armed at start), (#4747 disarm on destroy)", - "packages/objectql/src/engine.ts (SYSTEM_LEDGER_LIFECYCLE_CLASSES = audit|telemetry|event; transient deliberately absent) + the getDriver §3.6 class-routing priority note", - "packages/spec/src/data/object.zod.ts (LifecycleClassSchema, 5 members)", - "packages/services/service-automation/src/sys-automation-run.object.ts (telemetry 30d onlyWhen terminal — the MIXED-table rationale in its own comment), packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts (archive 90d/7y + the never-hot-delete note), packages/services/service-messaging/src/objects/inbox-message.object.ts (transient), http-delivery.object.ts (telemetry)", + "packages/objectql/src/lifecycle/lifecycle-service.ts#SYSTEM_CTX (the ADR-0057 enforcer: header contract — reaper/rotator/archiver, the never-hot-delete-unarchived safety rule, #5194 bounds; SYSTEM_CTX; DEFAULT_LIFECYCLE_SWEEP_MS 3_600_000; DEFAULT_LIFECYCLE_INITIAL_DELAY_MS 60_000; REAP_BATCH_SIZE 500 / REAP_MAX_BATCHES_PER_SWEEP 20; enabled getter incl. OS_LIFECYCLE_DISABLED; start()/timer; archive safety; the aggregate sweep line)", + "packages/objectql/src/plugin.ts#registered (registered as service 'lifecycle' by the engine plugin — every kernel with data has enforcement; a declared retention driving no sweeper is ADR-0049 dead surface), (armed at start), (#4747 disarm on destroy)", + "packages/objectql/src/engine.ts#SYSTEM_LEDGER_LIFECYCLE_CLASSES (SYSTEM_LEDGER_LIFECYCLE_CLASSES = audit|telemetry|event; transient deliberately absent) + the getDriver §3.6 class-routing priority note", + "packages/spec/src/data/object.zod.ts#LifecycleClassSchema (LifecycleClassSchema, 5 members)", + "packages/services/service-automation/src/sys-automation-run.object.ts#onlyWhen (telemetry 30d onlyWhen terminal — the MIXED-table rationale in its own comment), packages/plugins/plugin-audit/src/objects/sys-audit-log.object.ts (archive 90d/7y + the never-hot-delete note), packages/services/service-messaging/src/objects/inbox-message.object.ts (transient), http-delivery.object.ts (telemetry)", "ADR-0057 (§3.3 single enforcer, system context, isolation; §3.6 class separation), #5194 (bounded reap), #2834 (the automation-run declaration)" ], "history": [ @@ -2458,12 +2458,12 @@ }, "traps": ["destructive-in-place", "seed-data-thin", "silent-coercion"], "source": [ - "packages/spec/src/data/seed.zod.ts (SeedMode — the five modes with their declared semantics, incl. replace's 'Delete ALL records … Dangerous' claim)", - "packages/metadata-protocol/src/seed-loader.ts (mode = dataset.mode || config.defaultMode), (existing-records preload for upsert/update/ignore ONLY — insert/replace never match), (writeRecord per-mode switch; the replace arm's bare insert + 'caller should have cleared the table' comment), (decideWriteAction — the batched mirror, replace → insert),+ (isNoOpReplay — seed-declared fields only, conservative on doubt)", - "packages/spec/src/data/seed-loader.zod.ts (defaultMode default 'upsert')", - "packages/metadata-protocol/src/protocol.ts+ (applySeedBodies — published seed bodies materialize through the same loader; failures returned, never thrown, surfaced as seedApplied)", - "packages/runtime/src/seed-loader.ts (the MOVED shim — one SeedLoaderService for every path)", - "packages/spec/src/kernel/metadata-plugin.zod.ts (seed: allowRuntimeCreate true — the scratch-draft fixture path)", + "packages/spec/src/data/seed.zod.ts#SeedMode (SeedMode — the five modes with their declared semantics, incl. replace's 'Delete ALL records … Dangerous' claim)", + "packages/metadata-protocol/src/seed-loader.ts#writeRecord (mode = dataset.mode || config.defaultMode), (existing-records preload for upsert/update/ignore ONLY — insert/replace never match), (writeRecord per-mode switch; the replace arm's bare insert + 'caller should have cleared the table' comment), (decideWriteAction — the batched mirror, replace → insert),+ (isNoOpReplay — seed-declared fields only, conservative on doubt)", + "packages/spec/src/data/seed-loader.zod.ts#defaultMode (defaultMode default 'upsert')", + "packages/metadata-protocol/src/protocol.ts#applySeedBodies+ (applySeedBodies — published seed bodies materialize through the same loader; failures returned, never thrown, surfaced as seedApplied)", + "packages/runtime/src/seed-loader.ts#SeedLoaderService (the MOVED shim — one SeedLoaderService for every path)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#allowRuntimeCreate (seed: allowRuntimeCreate true — the scratch-draft fixture path)", "platform-core.seed-integrity (owns the stock boot-seed baselines this item must not contaminate)" ], "history": [ diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index c46fa20297..3a5d9b6d81 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -28,7 +28,7 @@ "enableOnInstall": true }, "expect": "2xx with the installed package echoed back. A re-run against a live DB 409s on the duplicate id — send `overwrite: true` deliberately rather than adding it by reflex (the guard exists so a second install never silently clobbers the first).", - "source": "POST /api/v1/packages is the dispatcher install route (packages/rest/src/rest-route-ledger.ts note; client `packages.install`); the body shape { manifest, enableOnInstall?, overwrite? } is URL- and body-pinned in packages/client/src/client.test.ts" + "source": "POST /api/v1/packages is the dispatcher install route (packages/rest/src/rest-route-ledger.ts#packages note; client `packages.install`); the body shape { manifest, enableOnInstall?, overwrite? } is URL- and body-pinned in packages/client/src/client.test.ts" }, { "step": 2, @@ -49,7 +49,7 @@ } }, "expect": "2xx. `enable.feeds` is stated EXPLICITLY false — unlike files it cannot be omitted, because omission means the spec default `true` (object.zod.ts) and would give a feeds-ENABLED object that scores both probes green for the wrong reason. `sharingModel` is public_read_write ON PURPOSE: the probes must be refused by the CAPABILITY gate, not by record access, and a caller who can read and edit this parent isolates FEEDS_DISABLED from the sibling RECORD_NOT_ACCESSIBLE refusal (packages/plugins/plugin-audit/src/comment-access-hooks.ts).", - "source": "`enable.feeds` is the #2707 opt-out, default true (packages/spec/src/data/object.zod.ts: 'Record comments/collaboration feed. Default on; explicit false hides the feed UI and rejects any write that makes a comment target this object (403 FEEDS_DISABLED) — a new comment and an update that re-threads an existing one alike'). `sharingModel` is a TOP-LEVEL object key, enum ['private','public_read','public_read_write','controlled_by_parent'] (packages/spec/src/data/object.zod.ts; ADR-0090 D4). `?package=` is read as `query.package` and threaded to saveMetaItem as packageId (packages/runtime/src/domains/meta.ts)" + "source": "`enable.feeds` is the #2707 opt-out, default true (packages/spec/src/data/object.zod.ts#Record: 'Record comments/collaboration feed. Default on; explicit false hides the feed UI and rejects any write that makes a comment target this object (403 FEEDS_DISABLED) — a new comment and an update that re-threads an existing one alike'). `sharingModel` is a TOP-LEVEL object key, enum ['private','public_read','public_read_write','controlled_by_parent'] (packages/spec/src/data/object.zod.ts#query; ADR-0090 D4). `?package=` is read as `query.package` and threaded to saveMetaItem as packageId (packages/runtime/src/domains/meta.ts)" } ], "teardown": "DELETE /api/v1/packages/com.objectstack.qa.feeds (client `packages.uninstall`, ledgered at rest-route-ledger.ts) — or simply discard the isolated file DB, which is the cheaper path and the one an isolated boot makes free. Any throwaway qa_nofeeds record and any sys_comment row created against it go with the package.", @@ -160,7 +160,7 @@ "dogfood-verification skill §3", "examples/app-showcase/src/data/objects/account.object.ts (requiredness + format/conditional validations)", "packages/runtime/src/route-ledger.ts (/data CRUD routes)", - "packages/rest/src/rest-server.ts (POST /data/:object/:id/clone → registerDataActionEndpoints) + packages/metadata-protocol/src/protocol.ts (cloneData: enable.clone gate, findOne-in-caller-context, CLONE_STRIP_FIELDS, stripReadonlyForInsert)", + "packages/rest/src/rest-server.ts#registerDataActionEndpoints (POST /data/:object/:id/clone → registerDataActionEndpoints) + packages/metadata-protocol/src/protocol.ts#cloneData (cloneData: enable.clone gate, findOne-in-caller-context, CLONE_STRIP_FIELDS, stripReadonlyForInsert)", "packages/rest/src/rest-route-ledger.ts (POST /api/v1/data/:object/:id/clone, client data.clone)", "objectui: e2e/live/record-history-display.spec.ts", "cross-ref: the inline-edit atomic two-surface behavior (ONE Save bar / ONE PATCH carrying exactly the changed keys + ifMatch) is folded into records-forms.concurrent-edit-conflict, not here" @@ -347,9 +347,9 @@ "ref": "packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts vectors, field-zoo-value-shape.test.ts contract)" }, "source": [ - "packages/spec/src/data/field.zod.ts (FieldType enum — 49 members, listed exhaustively in variants)", + "packages/spec/src/data/field.zod.ts#FieldType (FieldType enum — 49 members, listed exhaustively in variants)", "examples/app-showcase/src/data/objects/field-zoo.object.ts", - "examples/app-showcase/src/data/seed/index.ts (Specimen rows; expense/invoice roll-up expectations)", + "examples/app-showcase/src/data/seed/index.ts#Specimen (Specimen rows; expense/invoice roll-up expectations)", "packages/qa/dogfood/test/field-zoo.matrix.ts", "objectui: e2e/live/summary-rollup.spec.ts" ], @@ -494,11 +494,11 @@ "ref": "objectui: e2e/live/saved-view-filter.spec.ts, e2e/live/user-filters.spec.ts, e2e/live/inline-edit-polish-2572.spec.ts; packages/qa/dogfood/test/showcase-search.dogfood.test.ts" }, "source": [ - "examples/app-showcase/src/ui/views/task.view.ts (saved views, sort string form, inlineEdit, exportOptions, bulk/row actions, allowedVisualizations)", - "examples/app-showcase/src/ui/views/field-zoo.view.ts (conditionalFormatting, gated bulk actions)", - "packages/spec/src/ui/view.zod.ts (UserActionsConfigSchema, AppearanceConfigSchema/VisualizationTypeSchema)", + "examples/app-showcase/src/ui/views/task.view.ts#inlineEdit (saved views, sort string form, inlineEdit, exportOptions, bulk/row actions, allowedVisualizations)", + "examples/app-showcase/src/ui/views/field-zoo.view.ts#conditionalFormatting (conditionalFormatting, gated bulk actions)", + "packages/spec/src/ui/view.zod.ts#UserActionsConfigSchema (UserActionsConfigSchema, AppearanceConfigSchema/VisualizationTypeSchema)", "packages/spec/src/data/pagination-conformance.ts (deterministic paging property)", - "examples/app-showcase/src/data/seed/index.ts (10 tasks / 2 in_progress; CJK rows)" + "examples/app-showcase/src/data/seed/index.ts#tasks (10 tasks / 2 in_progress; CJK rows)" ], "history": [ { @@ -603,8 +603,8 @@ "ref": "examples/app-showcase/e2e/showcase-smoke.spec.ts (page-level render for the view surfaces)" }, "source": [ - "packages/spec/src/ui/view.zod.ts (ListViewSchema type enum: grid|kanban|gallery|calendar|timeline|gantt|map|chart|tree)", - "examples/app-showcase/src/coverage.ts (LIST_VIEW_TYPES — the 8 the task object demonstrates)", + "packages/spec/src/ui/view.zod.ts#ListViewSchema (ListViewSchema type enum: grid|kanban|gallery|calendar|timeline|gantt|map|chart|tree)", + "examples/app-showcase/src/coverage.ts#LIST_VIEW_TYPES (LIST_VIEW_TYPES — the 8 the task object demonstrates)", "examples/app-showcase/src/ui/views/task.view.ts", "examples/app-showcase/src/ui/views/business-unit.view.ts (tree)", "examples/app-showcase/src/data/seed/index.ts (view-feeding seed shape)" @@ -696,9 +696,9 @@ "automation-input" ], "source": [ - "packages/spec/src/ui/view.zod.ts (FormViewSchema type enum: simple|tabbed|wizard|split|drawer|modal)", - "examples/app-showcase/src/coverage.ts (FORM_VIEW_TYPES — the 5 authored)", - "examples/app-showcase/src/ui/views/task.view.ts (formViews incl. visibleWhen on notes)" + "packages/spec/src/ui/view.zod.ts#FormViewSchema (FormViewSchema type enum: simple|tabbed|wizard|split|drawer|modal)", + "examples/app-showcase/src/coverage.ts#FORM_VIEW_TYPES (FORM_VIEW_TYPES — the 5 authored)", + "examples/app-showcase/src/ui/views/task.view.ts#formViews (formViews incl. visibleWhen on notes)" ], "history": [ { @@ -793,8 +793,8 @@ "ref": "objectui: e2e/live/field-conditional-rules.spec.ts, e2e/live/required-when-submit.spec.ts, e2e/live/list-row-action-cel.spec.ts" }, "source": [ - "examples/app-showcase/src/data/objects/invoice.object.ts (the B2 rules + userActions gating, with server-semantics comments)", - "packages/spec/src/data/field.zod.ts (requiredWhen/readonlyWhen/visibleWhen authoring surface)", + "examples/app-showcase/src/data/objects/invoice.object.ts#userActions (the B2 rules + userActions gating, with server-semantics comments)", + "packages/spec/src/data/field.zod.ts#requiredWhen (requiredWhen/readonlyWhen/visibleWhen authoring surface)", "packages/objectql/src/validation/rule-validator.ts (enforcement site)" ], "history": [ @@ -983,7 +983,7 @@ }, "source": [ "examples/app-showcase/src/data/objects/cascading-select.object.ts (the B3 / #1583 fixture, with both-sides contract in its header comment)", - "examples/app-showcase/src/data/objects/invoice.object.ts (contact dependsOn account)", + "examples/app-showcase/src/data/objects/invoice.object.ts#dependsOn (contact dependsOn account)", "examples/app-showcase/src/data/seed/index.ts (contact spread per account)" ], "history": [ @@ -1083,7 +1083,7 @@ }, "source": [ "#3358 §4", - "examples/app-showcase/src/data/objects/invoice.object.ts (inlineEdit grid, amount expression, total summary)", + "examples/app-showcase/src/data/objects/invoice.object.ts#inlineEdit (inlineEdit grid, amount expression, total summary)", "examples/app-showcase/src/data/seed/index.ts (product catalog rows)" ], "history": [ @@ -1195,10 +1195,10 @@ }, "source": [ "#3358 §4 (evidence run captured exactly this trace)", - "examples/app-showcase/src/data/seed/index.ts (the 26-contact Northwind fixture, authored for objectui#2711)", - "examples/app-showcase/src/data/objects/invoice.object.ts (relatedList declaration)", + "examples/app-showcase/src/data/seed/index.ts#Northwind (the 26-contact Northwind fixture, authored for objectui#2711)", + "examples/app-showcase/src/data/objects/invoice.object.ts#relatedList (relatedList declaration)", "objectui: packages/app-shell/src/views/RecordDetailView.tsx (deriveRelatedLists canRead filter — object-level READ gate, objectui#2359)", - "examples/app-showcase/src/security/permission-sets.ts (showcase_manager child read vs showcase_contributor's omission); PENDING-GAPS §E3 / objectui#2565" + "examples/app-showcase/src/security/permission-sets.ts#showcase_manager (showcase_manager child read vs showcase_contributor's omission); PENDING-GAPS §E3 / objectui#2565" ], "history": [ { @@ -1386,7 +1386,7 @@ "source": [ "#3358 §4 ('leaving it unticked on the strength of a label')", "ADR-0059", - "examples/app-showcase/src/ui/actions/index.ts (maxSize declarations)" + "examples/app-showcase/src/ui/actions/index.ts#maxSize (maxSize declarations)" ], "history": [ { @@ -1579,9 +1579,9 @@ "ref": "objectui: e2e/live/list-row-action-cel.spec.ts (row-level CEL visibility) + e2e/live/action-modal.spec.ts (dialog dispatch)" }, "source": [ - "packages/spec/src/ui/action.zod.ts (ACTION_LOCATIONS — the canonical 6-value enum, single source of truth; `global_nav` was the 7th until #6888 retired it — no product surface rendered it)", - "examples/app-showcase/src/ui/actions/index.ts (per-location fixture fleet + the record:quick_actions filter note + the two headless `locations: []` declarations)", - "packages/lint/src/validate-action-locations.ts (the `action-no-placement` rule — this repo's codification of the current contract: an action with no `locations` renders nowhere and is therefore inert, while `locations: []` is the deliberate headless declaration and is NOT flagged)", + "packages/spec/src/ui/action.zod.ts#ACTION_LOCATIONS (ACTION_LOCATIONS — the canonical 6-value enum, single source of truth; `global_nav` was the 7th until #6888 retired it — no product surface rendered it)", + "examples/app-showcase/src/ui/actions/index.ts#locations (per-location fixture fleet + the record:quick_actions filter note + the two headless `locations: []` declarations)", + "packages/lint/src/validate-action-locations.ts#action (the `action-no-placement` rule — this repo's codification of the current contract: an action with no `locations` renders nowhere and is therefore inert, while `locations: []` is the deliberate headless declaration and is NOT flagged)", "objectui: packages/types/src/ui-action.ts `actionRendersAt` (the single placement predicate — a membership test, so an undeclared or empty `locations` matches nothing) + packages/.../action-bar.tsx, its consumer since objectui#3142", "cross-ref: bulk dispatch-count semantics live in records-forms.list-view-capabilities (bulk-actions variant); param dialogs in records-forms.action-param-widgets" ], @@ -1702,8 +1702,8 @@ "wrong-persona" ], "source": [ - "packages/spec/src/data/validation.zod.ts (ValidationRuleSchema, 6 discriminated variants; severity enum ['error','warning','info'] default 'error')", - "packages/objectql/src/validation/rule-validator.ts (evaluateRule switch + per-type codes; 'only error blocks', unevaluable-advisory stays non-blocking, the severity fork)", + "packages/spec/src/data/validation.zod.ts#ValidationRuleSchema (ValidationRuleSchema, 6 discriminated variants; severity enum ['error','warning','info'] default 'error')", + "packages/objectql/src/validation/rule-validator.ts#evaluateRule (evaluateRule switch + per-type codes; 'only error blocks', unevaluable-advisory stays non-blocking, the severity fork)", "examples/app-showcase/src/data/objects/{account,project,task}.object.ts (the seeded rules; project.object.ts the advisory project_health_progression)", "#1475 (declared ≠ enforced history: 9 declared → 6 declared+enforced)" ], @@ -1796,7 +1796,7 @@ "source": [ "#3358 §8 (the three never-imported gantt rows)", "objectui: packages/plugin-gantt/src/GanttView.tsx + scheduling.ts (RescheduleResult.skippedLocked)", - "packages/spec/src/ui/view.zod.ts (GanttConfigSchema)", + "packages/spec/src/ui/view.zod.ts#GanttConfigSchema (GanttConfigSchema)", "examples/app-showcase/src/ui/views/task.view.ts (stock fixture limits)" ], "history": [ @@ -1866,7 +1866,7 @@ ], "source": [ "objectui: packages/plugin-kanban/src/KanbanImpl.tsx (onDragEnd)", - "examples/app-showcase/src/data/objects/task.object.ts (task_status_flow state_machine)" + "examples/app-showcase/src/data/objects/task.object.ts#task_status_flow (task_status_flow state_machine)" ], "history": [ { @@ -2191,7 +2191,7 @@ "source": [ "objectui: packages/plugin-calendar/src/ObjectCalendar.tsx (handleEventDropDefault optimistic update + rollback + toast; empty-day quick-create → dataSource.create)", "objectui: packages/plugin-calendar/src/CalendarView.tsx + CalendarView.dnd.test.tsx", - "examples/app-showcase/src/ui/views/task.view.ts (Calendar view: startDateField due_date, colorField status)", + "examples/app-showcase/src/ui/views/task.view.ts#startDateField (Calendar view: startDateField due_date, colorField status)", "cross-ref: records-forms.view-type-gallery (calendar render-only), records-forms.kanban-drag-persistence / gantt-interactions (sibling drag-persist items)" ], "history": [ @@ -2296,7 +2296,7 @@ "source": [ "objectui: packages/plugin-detail/src/ConcurrentUpdateDialog.tsx (Reload/Overwrite UX), packages/plugin-form/src/occSave.tsx (If-Match → 409, conflict dialog, re-key overwrite)", "objectui: packages/plugin-detail/src/InlineEditSaveBar.tsx + packages/react/src/context/InlineEditContext.tsx (one save bar, changed-keys draft, ifMatch)", - "framework: packages/rest/src/rest-server.ts (CONCURRENT_UPDATE → 409 mapping), packages/metadata-protocol/src/protocol.ts updateData (If-Match / expectedVersion)", + "framework: packages/rest/src/rest-server.ts#mapping (CONCURRENT_UPDATE → 409 mapping), packages/metadata-protocol/src/protocol.ts#updateData updateData (If-Match / expectedVersion)", "PENDING-GAPS §C concurrent-edit-conflict + §E1 inline-edit atomic two-surface (objectui#2542/2549/2604); cross-ref records-forms.crud-roundtrip" ], "history": [ @@ -2441,9 +2441,9 @@ "objectui: packages/plugin-detail/src/{RecordChatterPanel,CommentInput,MentionAutocomplete,extractMentions}.tsx (compose + @mention)", "objectui: packages/app-shell/src/views/RecordDetailView.tsx (mergeFeedRows union-by-id, sys_comment + sys_activity fetch/merge, mentionSuggestions), packages/plugin-detail/src/renderers/recordActivityFeed.ts (activity→feed map)", "objectui: packages/collaboration/src/useMentionNotifications.ts (recipient-scoped bell, unreadCount)", - "framework: packages/spec/src/data/object.zod.ts (enable.feeds default true → FEEDS_DISABLED, 'a new comment and an update that re-threads an existing one alike'); PENDING-GAPS §B record-discussion-mentions", - "framework: packages/plugins/plugin-audit/src/audit-writers.ts (enforceFeedsCapability, registered on beforeInsert AND beforeUpdate per #10170), packages/plugins/plugin-audit/src/comment-access-hooks.ts (RECORD_NOT_ACCESSIBLE — the sibling refusal the probes isolate against), packages/plugins/plugin-audit/src/objects/sys-comment.object.ts (thread_id is conventionally `{object}:{record_id}`)", - "framework: packages/spec/src/data/feed.zod.ts (FeedFilterMode, 4 members — pinned above),+ (SYS_ACTIVITY_BUILTIN_TYPES + the #11507 open-vocabulary ruling: built-ins are the floor, never the ceiling); packages/plugins/plugin-audit/src/objects/sys-activity.object.ts (apiMethods ['get','list'] — why the probe cannot direct-POST)", + "framework: packages/spec/src/data/object.zod.ts#FEEDS_DISABLED (enable.feeds default true → FEEDS_DISABLED, 'a new comment and an update that re-threads an existing one alike'); PENDING-GAPS §B record-discussion-mentions", + "framework: packages/plugins/plugin-audit/src/audit-writers.ts#enforceFeedsCapability (enforceFeedsCapability, registered on beforeInsert AND beforeUpdate per #10170), packages/plugins/plugin-audit/src/comment-access-hooks.ts#RECORD_NOT_ACCESSIBLE (RECORD_NOT_ACCESSIBLE — the sibling refusal the probes isolate against), packages/plugins/plugin-audit/src/objects/sys-comment.object.ts#thread_id (thread_id is conventionally `{object}:{record_id}`)", + "framework: packages/spec/src/data/feed.zod.ts#FeedFilterMode (FeedFilterMode, 4 members — pinned above),+ (SYS_ACTIVITY_BUILTIN_TYPES + the #11507 open-vocabulary ruling: built-ins are the floor, never the ceiling); packages/plugins/plugin-audit/src/objects/sys-activity.object.ts#apiMethods (apiMethods ['get','list'] — why the probe cannot direct-POST)", "objectui: packages/plugin-detail/src/RecordActivityTimeline.tsx (FeedFilterMode imported from the spec), (the four filter options), (filterItems); packages/plugin-detail/src/renderers/recordActivityFeed.ts (ACTIVITY_TYPE_TO_FEED_TYPE superset map + UNMAPPED_ACTIVITY_FEED_TYPE fallback + its diagnostic; drift held by the objectui#5969 two-directional pin, recordActivityFeed.test.ts)" ], "automated": { @@ -2739,7 +2739,7 @@ }, "source": [ "objectui: packages/plugin-grid/src/ObjectGrid.tsx (selectAllMatching, resolveBulkRows paged fan-out + HARD_CAP, resetSelection #3056, totalMatching), packages/plugin-grid/src/components/BulkActionBar.tsx (cross-page banner)", - "examples/app-showcase/src/data/seed/index.ts (Northwind 26-contact fixture, objectui#2711)", + "examples/app-showcase/src/data/seed/index.ts#Northwind (Northwind 26-contact fixture, objectui#2711)", "PENDING-GAPS §C bulk-select-all-matching; cross-ref records-forms.list-view-capabilities (bulk-actions, page-local)" ], "history": [ @@ -2828,7 +2828,7 @@ }, "source": [ "objectui: packages/fields/src/widgets/LookupField.tsx (allowCreate default-on, SYSTEM_REFERENCE_RX / USER_DIRECTORY_REFS, isUserFacingReference, handleCreateNew), packages/fields/src/widgets/RecordPickerDialog.tsx (scoped find)", - "examples/app-showcase/src/data/objects/business-unit.object.ts + category.object.ts (parent allowCreate:true)", + "examples/app-showcase/src/data/objects/business-unit.object.ts#allowCreate + category.object.ts (parent allowCreate:true)", "PENDING-GAPS §B lookup-picker-create-new" ], "history": [ @@ -3006,7 +3006,7 @@ "ref": "objectui: e2e/import-console/import-console-undo.spec.ts, e2e/import-harness/import-undo.spec.ts (both self-gate)" }, "source": [ - "framework: packages/rest/src/rest-route-ledger.ts (POST /data/:object/import/jobs; /import/jobs/:jobId/{cancel,undo,results}; GET /import/jobs[/:jobId])", + "framework: packages/rest/src/rest-route-ledger.ts#jobId (POST /data/:object/import/jobs; /import/jobs/:jobId/{cancel,undo,results}; GET /import/jobs[/:jobId])", "objectui: e2e/import-console/import-console-undo.spec.ts, e2e/import-harness/import-undo.spec.ts", "PENDING-GAPS §C import-job-undo-cancel" ], @@ -3163,14 +3163,14 @@ "ref": "packages/qa/dogfood/test/showcase-public-form.dogfood.test.ts (pins the beforeInsert stamp: status='new'/source='web' on an anonymous inquiry submit); packages/qa/dogfood/test/hook-error-format.dogfood.test.ts (pins the beforeDelete onError:'abort' throw → REST error body). Declarative-wrapper semantics (two-root condition, async, retry, onError, fail-closed) are unit-pinned in packages/objectql/src/hook-wrappers.ts + hook-binder.ts tests. The transition audit / over-budget warn / priority-ordering LOG oracles and the abort/log/priority SCRATCH variants are NOT yet dogfood-pinned — drive them by hand." }, "source": [ - "examples/app-showcase/src/data/hooks/index.ts (the four fixture hooks + allHooks export; header comments spell out the two-root #4784 transition and the != null / not has() #4770 rationale verbatim)", - "packages/spec/src/data/hook.zod.ts (HookSchema + HookEvent enum beforeFind/afterFind/beforeInsert/afterInsert/beforeUpdate/afterUpdate/beforeDelete/afterDelete; defineHook; async 'after* only'; onError default 'abort'; empty-target refusal #4001)", - "packages/objectql/src/hook-wrappers.ts (wrapDeclarativeHook wrapping order condition→async→retry→timeout→onError; pickRecordPayload #4770 record = stored ⊕ payload total over declared fields; pickPreviousPayload #4784 previous binding; HookConditionError #4775 fail-closed, raised OUTSIDE onError; fireAndForget = async && isAfterEvent)", - "packages/objectql/src/hook-binder.ts (bindHooksToEngine: per-event × per-object engine.registerHook with priority; unresolved-body / empty-target skips)", - "packages/runtime/src/sandbox/body-runner.ts (log: engineCtx.logger — the ['log'] capability routing that makes the audit/warn lines an observable log oracle)", - "packages/spec/liveness/hook.json (object/events/body/priority/async/condition/retryPolicy/timeout/onError all 'live'; label/description 'dead' but kept as docs)", + "examples/app-showcase/src/data/hooks/index.ts#allHooks (the four fixture hooks + allHooks export; header comments spell out the two-root #4784 transition and the != null / not has() #4770 rationale verbatim)", + "packages/spec/src/data/hook.zod.ts#HookSchema (HookSchema + HookEvent enum beforeFind/afterFind/beforeInsert/afterInsert/beforeUpdate/afterUpdate/beforeDelete/afterDelete; defineHook; async 'after* only'; onError default 'abort'; empty-target refusal #4001)", + "packages/objectql/src/hook-wrappers.ts#wrapDeclarativeHook (wrapDeclarativeHook wrapping order condition→async→retry→timeout→onError; pickRecordPayload #4770 record = stored ⊕ payload total over declared fields; pickPreviousPayload #4784 previous binding; HookConditionError #4775 fail-closed, raised OUTSIDE onError; fireAndForget = async && isAfterEvent)", + "packages/objectql/src/hook-binder.ts#bindHooksToEngine (bindHooksToEngine: per-event × per-object engine.registerHook with priority; unresolved-body / empty-target skips)", + "packages/runtime/src/sandbox/body-runner.ts#engineCtx (log: engineCtx.logger — the ['log'] capability routing that makes the audit/warn lines an observable log oracle)", + "packages/spec/liveness/hook.json#retryPolicy (object/events/body/priority/async/condition/retryPolicy/timeout/onError all 'live'; label/description 'dead' but kept as docs)", "examples/app-showcase/src/data/objects/{task,inquiry,project}.object.ts (task.done boolean + task.title required; inquiry.status select['new' default:true]/source text no-default; project.budget/spent currency + spent_within_budget rule rejecting spent > budget*1.2)", - "examples/app-showcase/src/data/seed/index.ts (done tasks 'Audit current IA'/'App wireframes'; not-done 'Build homepage'; projects 'Data Platform' 600000/420000 and 'Website Relaunch' 150000/60000)", + "examples/app-showcase/src/data/seed/index.ts#tasks (done tasks 'Audit current IA'/'App wireframes'; not-done 'Build homepage'; projects 'Data Platform' 600000/420000 and 'Website Relaunch' 150000/60000)", "packages/qa/dogfood/test/showcase-public-form.dogfood.test.ts + hook-error-format.dogfood.test.ts (the two existing dogfood pins this item cites)" ], "history": [ @@ -3309,12 +3309,12 @@ "ref": "packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts vectors, field-zoo-value-shape.test.ts) for round-trip & masking; packages/objectql/src/engine-lookup-referential-integrity.test.ts for reference_not_found (#4441); packages/objectql/src/engine-autonumber-runtime-owned.test.ts for the autonumber strip (#5503)" }, "source": [ - "packages/objectql/src/validation/record-validator.ts (validateOne: max_length/min_length, min_value/max_value/invalid_number, invalid_option for select+multiselect, normalizeMultiValueFields; NO scale/precision rounding)", - "packages/spec/src/api/errors.zod.ts (FieldErrorCode catalog — required, max_length, min_value/max_value, invalid_option, reference_not_found, invalid_type … ADR-0114)", - "packages/spec/src/data/field.zod.ts (per-type constraints: maxLength/minLength, precision/scale/min/max, options value-vs-label, lookupFilters/dependsOn, autonumberFormat tokens, formula expression, summaryOperations)", - "packages/drivers/driver-sql/src/sql-driver.ts (createColumn: number/currency/percent → table.float, NOT DECIMAL; datetime → DATETIME(3)/timestamptz)", - "packages/objectql/src/engine.ts (assertReferencesResolve #4441 — existence-only, unscoped)", - "packages/objectql/src/validation/rule-validator.ts (RUNTIME_OWNED_FIELD_TYPES=['autonumber']; stripRuntimeOwnedFields/stripReadonlyFields; formula-vs-summary note #6014)", + "packages/objectql/src/validation/record-validator.ts#validateOne (validateOne: max_length/min_length, min_value/max_value/invalid_number, invalid_option for select+multiselect, normalizeMultiValueFields; NO scale/precision rounding)", + "packages/spec/src/api/errors.zod.ts#FieldErrorCode (FieldErrorCode catalog — required, max_length, min_value/max_value, invalid_option, reference_not_found, invalid_type … ADR-0114)", + "packages/spec/src/data/field.zod.ts#maxLength (per-type constraints: maxLength/minLength, precision/scale/min/max, options value-vs-label, lookupFilters/dependsOn, autonumberFormat tokens, formula expression, summaryOperations)", + "packages/drivers/driver-sql/src/sql-driver.ts#createColumn (createColumn: number/currency/percent → table.float, NOT DECIMAL; datetime → DATETIME(3)/timestamptz)", + "packages/objectql/src/engine.ts#assertReferencesResolve (assertReferencesResolve #4441 — existence-only, unscoped)", + "packages/objectql/src/validation/rule-validator.ts#stripRuntimeOwnedFields (RUNTIME_OWNED_FIELD_TYPES=['autonumber']; stripRuntimeOwnedFields/stripReadonlyFields; formula-vs-summary note #6014)", "packages/spec/src/data/autonumber-format.ts (token grammar) + autonumber-format.test.ts", "examples/app-showcase/src/data/objects/field-zoo.object.ts, invoice.object.ts (status/account/total carriers)" ], @@ -3413,10 +3413,10 @@ "ref": "objectui: e2e/live/cascading-options.spec.ts (client clear-on-change); packages/objectql/src/validation/rule-validator.option-visibility.test.ts (server WRITTEN-value gate)" }, "source": [ - "packages/objectql/src/validation/rule-validator.ts (evaluateOptionVisibility — WRITTEN-value only via `!(name in data) continue`; fail-open on unevaluable; invalid_option/option_unavailable)", - "packages/objectql/src/engine.ts (assertReferencesResolve #4441 — existence-only, 'Why the probe is unscoped')", + "packages/objectql/src/validation/rule-validator.ts#evaluateOptionVisibility (evaluateOptionVisibility — WRITTEN-value only via `!(name in data) continue`; fail-open on unevaluable; invalid_option/option_unavailable)", + "packages/objectql/src/engine.ts#assertReferencesResolve (assertReferencesResolve #4441 — existence-only, 'Why the probe is unscoped')", "examples/app-showcase/src/data/objects/cascading-select.object.ts (2-level country→province; no 3rd level)", - "examples/app-showcase/src/data/objects/invoice.object.ts (contact dependsOn ['account'], account lookupFilters)", + "examples/app-showcase/src/data/objects/invoice.object.ts#dependsOn (contact dependsOn ['account'], account lookupFilters)", "examples/app-showcase/src/data/seed/index.ts (contact spread per account)", "objectui: e2e/live/cascading-options.spec.ts" ], @@ -3509,12 +3509,12 @@ "ref": "packages/objectql/src/secret-fields.test.ts (encrypt/mask/fail-closed); packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts masked vectors for f_secret & f_password)" }, "source": [ - "packages/objectql/src/secret-fields.ts (SECRET_MASK, SECRET_REF_PREFIX, collectSecretFields/collectMaskedReadFields/collectCredentialFields)", - "packages/objectql/src/engine.ts (encryptSecretFields — encrypt→sys_secret→ref, echoed-mask drop, fail-closed throw; decrypt path)", - "packages/spec/src/data/field.zod.ts (FieldType 'secret'/'password' ADR-0100 notes; ackPlaintextMasking)", - "packages/cli/src/commands/serve.ts (LocalCryptoProvider host wiring via setCryptoProvider)", + "packages/objectql/src/secret-fields.ts#SECRET_REF_PREFIX (SECRET_MASK, SECRET_REF_PREFIX, collectSecretFields/collectMaskedReadFields/collectCredentialFields)", + "packages/objectql/src/engine.ts#encryptSecretFields (encryptSecretFields — encrypt→sys_secret→ref, echoed-mask drop, fail-closed throw; decrypt path)", + "packages/spec/src/data/field.zod.ts#FieldType (FieldType 'secret'/'password' ADR-0100 notes; ackPlaintextMasking)", + "packages/cli/src/commands/serve.ts#host (LocalCryptoProvider host wiring via setCryptoProvider)", "packages/platform-objects/src/system/sys-secret.object.ts (ciphertext store)", - "examples/app-showcase/src/data/objects/field-zoo.object.ts (f_secret, f_password); packages/qa/dogfood/test/field-zoo.matrix.ts (masked vectors)" + "examples/app-showcase/src/data/objects/field-zoo.object.ts#f_secret (f_secret, f_password); packages/qa/dogfood/test/field-zoo.matrix.ts (masked vectors)" ], "history": [ { @@ -3600,10 +3600,10 @@ "automation-input" ], "source": [ - "packages/spec/src/data/object.zod.ts (visibleOn tombstone prescription), (visibleWhen, CEL via ExpressionInputSchema — #13030, commit 53dc739, Option-A maintainer ruling 2026-08-28)", + "packages/spec/src/data/object.zod.ts#visibleOn (visibleOn tombstone prescription), (visibleWhen, CEL via ExpressionInputSchema — #13030, commit 53dc739, Option-A maintainer ruling 2026-08-28)", "packages/spec/src/data/field-group-layout.ts (verbatim passthrough; evaluation ruled the renderer's), field-group-layout.test.ts+ and object-strictness-batch20.test.ts (the pins)", "objectui: packages/plugin-form/src/fieldGroups.ts and packages/plugin-detail/src/synth/buildDefaultPageSchema.ts — the two adapters that DROP the key at objectui 1e14d70 (clause 4's expected-fail); packages/components/src/renderers/form/form.tsx (#6236 section grouping contract) and packages/plugin-form/src/sectionPredicateDiagnostic.ts (wizard-arm design boundary) — the gating machinery derived sections would plug into", - "examples/app-showcase/src/data/objects/contact.object.ts, semantic-zoo.object.ts (stock fieldGroups declarations, none with a predicate)", + "examples/app-showcase/src/data/objects/contact.object.ts#fieldGroups, semantic-zoo.object.ts (stock fieldGroups declarations, none with a predicate)", "cross-ref: field-level FormField.visibleWhen is form-view-gallery's; view-section FormSection.visibleWhen is NOT this slot — this item is the object-level fieldGroups predicate #13030 re-introduced" ], "history": [ @@ -3699,10 +3699,10 @@ "ref": "packages/drivers/driver-memory/src/memory-unique-constraint.test.ts (refusal shape, NULL-distinct, every write path, scope matrix, autonumber converge, boundary cases); packages/drivers/driver-sql/src/sql-driver-8577-tenant-scoped-declared-unique.test.ts + sql-driver-declared-index-organization-respelling.test.ts (SQL family, 409 UNIQUE_VIOLATION over real REST)" }, "source": [ - "packages/drivers/driver-memory/src/memory-unique-constraint.ts (#13197/#13249, commit 56c093c — single judgment point; scope table read off driver-sql, arm for arm)", - "packages/drivers/driver-memory/src/memory-driver.ts (updateMany check-before-mutate)", - "packages/drivers/driver-sql/src/schema-drift.ts (uniqueIndexesFromFields, ADR-0120 D1/D3)", - "packages/objectql/src/engine.ts (createWithAutonumberResync — the branch the enforcement makes reachable)", + "packages/drivers/driver-memory/src/memory-unique-constraint.ts#scope (#13197/#13249, commit 56c093c — single judgment point; scope table read off driver-sql, arm for arm)", + "packages/drivers/driver-memory/src/memory-driver.ts#updateMany (updateMany check-before-mutate)", + "packages/drivers/driver-sql/src/schema-drift.ts#uniqueIndexesFromFields (uniqueIndexesFromFields, ADR-0120 D1/D3)", + "packages/objectql/src/engine.ts#createWithAutonumberResync (createWithAutonumberResync — the branch the enforcement makes reachable)", "content/docs/capabilities/data.mdx ('Uniqueness … per-field switches' — the docs claim this item makes testable)" ], "history": [ @@ -3809,8 +3809,8 @@ "ref": "packages/objectql/src/engine-cascade-delete.test.ts (+ engine-cascade-delete-atomic.test.ts, engine-cascade-delete-multivalue-probe.test.ts) — defaults, coercion+log, escalation, per-row multi-value narrowing" }, "source": [ - "packages/spec/src/data/field.zod.ts (deleteBehavior inline enum; #9689 parse rejection, #9784 default materialization)", - "packages/objectql/src/engine.ts (defaults, coercion + [cascade-delete] log, required-FK escalation incl. #9688 multi-value deferral), (DELETE_RESTRICTED envelope, #12166 count disclosure, cascade recursion)", + "packages/spec/src/data/field.zod.ts#deleteBehavior (deleteBehavior inline enum; #9689 parse rejection, #9784 default materialization)", + "packages/objectql/src/engine.ts#required (defaults, coercion + [cascade-delete] log, required-FK escalation incl. #9688 multi-value deferral), (DELETE_RESTRICTED envelope, #12166 count disclosure, cascade recursion)", "examples/app-showcase/src/data/objects/invoice.object.ts,184; contact.object.ts; field-zoo.object.ts; expense-report.object.ts (the four stock arms)" ], "history": [ @@ -3898,7 +3898,7 @@ "ref": "packages/metadata-protocol — search-clone-schema-conformance.test.ts (parses the real cloneData producer); packages/rest — search-clone-route-schema-conformance.test.ts (drives the live mount); named on the ledger row rest-route-ledger.ts" }, "source": [ - "packages/metadata-protocol/src/protocol.ts (cloneData: registration gate #3770, CLONE_DISABLED, findOne-in-context, CLONE_STRIP_FIELDS + system/autonumber/formula/summary strip, overrides, stripReadonlyForInsert #3043, omitInternalFieldsFromWriteResponse #7823)", + "packages/metadata-protocol/src/protocol.ts#cloneData (cloneData: registration gate #3770, CLONE_DISABLED, findOne-in-context, CLONE_STRIP_FIELDS + system/autonumber/formula/summary strip, overrides, stripReadonlyForInsert #3043, omitInternalFieldsFromWriteResponse #7823)", "packages/rest/src/rest-route-ledger.ts (POST /api/v1/data/:object/:id/clone, client data.clone, bare-201 note #11924)", "objectui: packages/app-shell/src/views/studio-design/ObjectSettingsPanel.tsx (Studio authors the enable.clone opt-out switch)", "cross-ref: the clone HAPPY path + engine-column re-derivation + RLS-gated 404 are crud-roundtrip clauses 7-8 — this item drives only the contract edges that item does not, and deliberately re-states none of its oracles" @@ -3987,8 +3987,8 @@ "ref": "packages/plugins/plugin-audit/src/audit-lookup-summary.test.ts (reference titles + read plan); audit-option-label-summary.test.ts (option labels); objectui e2e/live/record-history-display.spec.ts (tab display contract, cited by crud-roundtrip)" }, "source": [ - "packages/plugins/plugin-audit/src/audit-writers.ts (planTrackedLookupReads), (renderTrackedChangeSummary — ADR-0052 §5b, #7230/#7289 localization), (zero-reads-by-default plan)", - "packages/spec/src/data/object.zod.ts (history derives from get ∧ trackHistory), (opt-in default false); packages/spec/src/data/api-derivation.ts", + "packages/plugins/plugin-audit/src/audit-writers.ts#planTrackedLookupReads (planTrackedLookupReads), (renderTrackedChangeSummary — ADR-0052 §5b, #7230/#7289 localization), (zero-reads-by-default plan)", + "packages/spec/src/data/object.zod.ts#trackHistory (history derives from get ∧ trackHistory), (opt-in default false); packages/spec/src/data/api-derivation.ts", "objectui: packages/app-shell/src/views/RecordDetailView.tsx (History tab three-precondition gate)", "examples/app-showcase/src/data/objects/account.object.ts (the deliberate both-halves fixture + its comment ruling the tab gate), task.object.ts,60" ], @@ -4080,9 +4080,9 @@ "expect": 7 }, "source": [ - "packages/spec/src/data/mapping.zod.ts (TransformType — none/constant/lookup/split/join/javascript/map, 7 members, named export pinned above)", - "packages/rest/src/import-mapping.ts (format guard + the javascript refusal naming #2611), (the per-transform switch + around-the-seam unknown-transform refusal)", - "examples/app-showcase/src/data/mappings/index.ts (showcase_inquiry_feed — the stock none+map carrier)", + "packages/spec/src/data/mapping.zod.ts#TransformType (TransformType — none/constant/lookup/split/join/javascript/map, 7 members, named export pinned above)", + "packages/rest/src/import-mapping.ts#transform (format guard + the javascript refusal naming #2611), (the per-transform switch + around-the-seam unknown-transform refusal)", + "examples/app-showcase/src/data/mappings/index.ts#showcase_inquiry_feed (showcase_inquiry_feed — the stock none+map carrier)", "cross-ref: records-forms.named-import-mapping (header mapping, idempotence, unknown-name refusal — kept there; this item is the transform axis)" ], "history": [ diff --git a/docs/qa/platform-checklist/areas/search.json b/docs/qa/platform-checklist/areas/search.json index 08ef5a243c..8462ecaa0e 100644 --- a/docs/qa/platform-checklist/areas/search.json +++ b/docs/qa/platform-checklist/areas/search.json @@ -32,7 +32,7 @@ "step": 3, "call": "insert a sys_user_position row in system context binding that user id to the `contributor` position", "expect": "one row. This is THE step that makes the persona a subset reader: `contributor` resolves to showcase_contributor (auto-bound at boot, see requires), which grants showcase_invoice allowRead/allowCreate/allowEdit, and the RLS rule `invoice_own_rows` (using: `owner == current_user.email`, positions: ['contributor']) narrows every select to rows the caller owns.", - "source": "examples/app-showcase/src/security/permission-sets.ts — ContributorPermissionSet.objects.showcase_invoice and rowLevelSecurity['invoice_own_rows']; examples/app-showcase/src/security/positions.ts (the `contributor` position); bind-position-sets.ts for the position→set edge" + "source": "examples/app-showcase/src/security/permission-sets.ts#ContributorPermissionSet — ContributorPermissionSet.objects.showcase_invoice and rowLevelSecurity['invoice_own_rows']; examples/app-showcase/src/security/positions.ts#contributor (the `contributor` position); bind-position-sets.ts for the position→set edge" }, { "step": 4, @@ -131,8 +131,8 @@ "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/showcase-search.dogfood.test.ts" }, "source": [ "packages/qa/dogfood/test/search-conformance.ledger.ts (rows search-executor, search-select-label-mapping — the variants list is the enforced behavior set)", - "packages/objectql/src/search-filter.ts (docblock 'Matching:' paragraph — matching semantics: terms AND-ed, fields OR-ed, case-insensitive, label mapping; the DECLARATION this item transcribes, honored for textual types by fieldClausesForTerm since #7641 moved it onto `$icontains`)", - "packages/objectql/src/engine.ts expandSearchOnAst (the executor site the ledger names)", + "packages/objectql/src/search-filter.ts#fieldClausesForTerm (docblock 'Matching:' paragraph — matching semantics: terms AND-ed, fields OR-ed, case-insensitive, label mapping; the DECLARATION this item transcribes, honored for textual types by fieldClausesForTerm since #7641 moved it onto `$icontains`)", + "packages/objectql/src/engine.ts#expandSearchOnAst expandSearchOnAst (the executor site the ledger names)", "#4706 Q2 = A (the operator ruling that fixes the direction: `$contains` is a case-SENSITIVE substring test and `$icontains` is the case-insensitive one — so 'case-insensitive $search' means $icontains, and the declaration is what the executor must satisfy)" ], "history": [ @@ -196,8 +196,8 @@ "automated": { "kind": "api", "ref": "packages/qa/dogfood/test/showcase-search.dogfood.test.ts" }, "source": [ "packages/qa/dogfood/test/search-conformance.ledger.ts (row search-fields-override: intersection + ingress gate, #4254)", - "packages/metadata-protocol/src/protocol.ts (assertSearchFieldsAreSearchable — 400 INVALID_FIELD; $-spelling normalization)", - "packages/objectql/src/search-filter.ts (resolveSearchFields intersection; companion excluded from resolution — 'invisible to $searchFields overrides and to clients')" + "packages/metadata-protocol/src/protocol.ts#assertSearchFieldsAreSearchable (assertSearchFieldsAreSearchable — 400 INVALID_FIELD; $-spelling normalization)", + "packages/objectql/src/search-filter.ts#resolveSearchFields (resolveSearchFields intersection; companion excluded from resolution — 'invisible to $searchFields overrides and to clients')" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item from the conformance ledger's override row and the #4254 ingress-gate source, including the companion-invisibility clause from search-filter.ts", "ref": "claude/platform-test-checklist-ocwugl" } @@ -266,10 +266,10 @@ ], "traps": ["wrong-persona", "seed-data-thin"], "source": [ - "examples/app-showcase/src/security/permission-sets.ts (invoice_own_rows: owner == current_user.email, positions ['contributor'])", + "examples/app-showcase/src/security/permission-sets.ts#invoice_own_rows (invoice_own_rows: owner == current_user.email, positions ['contributor'])", "examples/app-showcase/src/data/seed/index.ts (INV-1001..1012 owner spread; 'ada sees INV-1001/1002's lines but never linus's INV-1003')", - "packages/objectql/src/engine.ts (expandSearchOnAst ANDs into ast.where; count() rides the read middleware — the #2737 total-leak fix)", - "packages/metadata-protocol/src/protocol.ts (searched lists: countable = search == null → page-local total from the scoped find)" + "packages/objectql/src/engine.ts#expandSearchOnAst (expandSearchOnAst ANDs into ast.where; count() rides the read middleware — the #2737 total-leak fix)", + "packages/metadata-protocol/src/protocol.ts#countable (searched lists: countable = search == null → page-local total from the scoped find)" ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item: RLS × $search both-personas contract; total-leakage clause grounded in the protocol's page-local total for searched lists and the #2737 count middleware fix, persona provisioning gap recorded honestly", "ref": "claude/platform-test-checklist-ocwugl" }, @@ -371,11 +371,11 @@ ], "traps": ["stale-dist", "seed-data-thin"], "source": [ - "packages/types/src/env.ts (resolveSearchPinyinEnabled — explicit env wins, truthy set {1,true,on,yes}; unset derives from zh-* locales; stampSearchPinyinEnabled at boot)", + "packages/types/src/env.ts#resolveSearchPinyinEnabled (resolveSearchPinyinEnabled — explicit env wins, truthy set {1,true,on,yes}; unset derives from zh-* locales; stampSearchPinyinEnabled at boot)", "packages/plugins/plugin-pinyin-search/src/pinyin-search-plugin.ts (inert-when-off, before-save hooks, kernel:bootstrapped backfill) — reached only when the host loads the plugin; under the CLI the flag decides `requires` first, which is why the roster, not this file's log line, is the OFF-side oracle", - "packages/objectql/src/search-companion.ts (__search companion: display-field-only materialization, FLS/secret eligibility gate)", + "packages/objectql/src/search-companion.ts#display (__search companion: display-field-only materialization, FLS/secret eligibility gate)", "packages/objectql/src/search-filter.ts (each latin term ORs { __search: { $contains: term } } — purely additive)", - "examples/app-showcase/src/data/seed/index.ts (张伟/华宁科技 seeded for exactly this demo) + examples/app-showcase/objectstack.config.ts (supportedLocales ['en','zh-CN'])", + "examples/app-showcase/src/data/seed/index.ts (张伟/华宁科技 seeded for exactly this demo) + examples/app-showcase/objectstack.config.ts#supportedLocales (supportedLocales ['en','zh-CN'])", "docs/plans/release-15.1-test-plan.md E5/G9 (#3027/#3034)" ], "history": [ @@ -449,7 +449,7 @@ ], "traps": ["hydration-race", "shared-browser-tab"], "source": [ - "packages/objectql/src/search-filter.ts + packages/objectql/src/engine.ts expandSearchOnAst ($search → $or of $contains ANDed into ast.where at query time — no index artifact for latin search)", + "packages/objectql/src/search-filter.ts + packages/objectql/src/engine.ts#expandSearchOnAst expandSearchOnAst ($search → $or of $contains ANDed into ast.where at query time — no index artifact for latin search)", "packages/plugins/plugin-pinyin-search/src/pinyin-search-plugin.ts (before-save recompute = write-through; kernel:bootstrapped backfill + rebuildSearchCompanion = the deferred reconcile paths)", "packages/qa/dogfood/test/search-conformance.ledger.ts (Tier 2 external engines deliberately absent — there is no FTS index to be stale)" ], @@ -559,7 +559,7 @@ "objectui packages/app-shell/src/views/SearchResultsPage.tsx (/apps/:appName/search — same searchAll path, record hits grouped by object + nav matches, designed empty state)", "objectui packages/react/src/hooks/useRecordSearch.ts (prefers dataSource.searchAll → GET /api/v1/search; per-object find({ $search }) fanout fallback when searchAll is absent)", "objectui packages/data-objectstack/src/index.ts (searchAll → GET /api/v1/search?q=, returns { query, hits }; 404 → empty when the search plugin is absent)", - "packages/rest/src/rest-route-ledger.ts (GET /api/v1/search, family search, source route-manager, client search) + packages/metadata-protocol/src/protocol.ts (searchAll backing)", + "packages/rest/src/rest-route-ledger.ts (GET /api/v1/search, family search, source route-manager, client search) + packages/metadata-protocol/src/protocol.ts#searchAll (searchAll backing)", "search.rls-both-personas (the RLS × $search read-path mechanism this item reuses) and search.cross-field-object-search (the /data $search path this item is DISTINCT from — this tests the console global-search UI + the /search route)" ], "history": [ diff --git a/docs/qa/platform-checklist/areas/studio-authoring.json b/docs/qa/platform-checklist/areas/studio-authoring.json index c4d49abf95..7d734fa6cb 100644 --- a/docs/qa/platform-checklist/areas/studio-authoring.json +++ b/docs/qa/platform-checklist/areas/studio-authoring.json @@ -224,8 +224,8 @@ ], "traps": ["stale-console-bundle", "hydration-race"], "source": [ - "packages/spec/src/ui/view.zod.ts (container keys list/form/listViews/formViews; the 'read by getViewsByObject() / GET /meta/view?object=' binding; guidance map for wrong-layer keys)", - "packages/spec/src/api/protocol.zod.ts (SaveMetaItemResponse: state draft|active, 'staged only — not served until published')", + "packages/spec/src/ui/view.zod.ts#listViews (container keys list/form/listViews/formViews; the 'read by getViewsByObject() / GET /meta/view?object=' binding; guidance map for wrong-layer keys)", + "packages/spec/src/api/protocol.zod.ts#SaveMetaItemResponse (SaveMetaItemResponse: state draft|active, 'staged only — not served until published')", "docs/audits/2026-07-studio-package-create-ux-dogfood.md (publish→live launcher/list inside one server session)" ], "history": [ @@ -432,12 +432,12 @@ "traps": ["hydration-race", "dispatcher-vs-hono-route"], "automated": { "kind": "e2e", "ref": "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" }, "source": [ - "packages/spec/src/api/protocol.zod.ts (SaveMetaItemResponse: state draft|active, version as If-Match/409 OCC token, 'staged only — not served until published')", - "packages/runtime/src/domains/packages.ts (POST /packages/:id/publish-drafts, ADR-0033/ADR-0045 visibility flip + its failure warning)", + "packages/spec/src/api/protocol.zod.ts#SaveMetaItemResponse (SaveMetaItemResponse: state draft|active, version as If-Match/409 OCC token, 'staged only — not served until published')", + "packages/runtime/src/domains/packages.ts#packages (POST /packages/:id/publish-drafts, ADR-0033/ADR-0045 visibility flip + its failure warning)", "packages/objectql/src/protocol-publish-package-drafts.test.ts (atomic namespace gate; #4463 author-time rules gate the draft→active transition)", - "packages/rest/src/rest-server.ts (GET /meta/:type/:name/{history,audit,diff} + POST .../rollback: overlay-type history vs { events: [] } for non-overlay; rollback body { toVersion }, 400 on a missing/invalid toVersion; actor from resolveMetaWriteActor — the authenticated session identity ONLY, [#7941] — X-Actor is ignored outright, not read as a fallback either)", - "packages/rest/src/meta-write-actor-identity.test.ts (unit pin: an explicit X-Actor does NOT outrank the authenticated identity, and is inert on the machine-write path too)", - "packages/rest/src/rest-route-ledger.ts (client bindings meta.getHistory / getAudit / diffItem / rollbackItem; the 'dispatcher /meta swallows /history as a compound name and 404s' note — routes hunter #12)", + "packages/rest/src/rest-server.ts#toVersion (GET /meta/:type/:name/{history,audit,diff} + POST .../rollback: overlay-type history vs { events: [] } for non-overlay; rollback body { toVersion }, 400 on a missing/invalid toVersion; actor from resolveMetaWriteActor — the authenticated session identity ONLY, [#7941] — X-Actor is ignored outright, not read as a fallback either)", + "packages/rest/src/meta-write-actor-identity.test.ts#Actor (unit pin: an explicit X-Actor does NOT outrank the authenticated identity, and is inert on the machine-write path too)", + "packages/rest/src/rest-route-ledger.ts#getHistory (client bindings meta.getHistory / getAudit / diffItem / rollbackItem; the 'dispatcher /meta swallows /history as a compound name and 404s' note — routes hunter #12)", "docs/audits/2026-07-studio-package-create-ux-dogfood.md ('Unpublished draft' badge, 'Changes · n', 'one atomic release' toast)" ], "history": [ @@ -510,8 +510,8 @@ ], "traps": ["stale-console-bundle", "automation-input"], "source": [ - "packages/runtime/src/http-dispatcher.test.ts (the located '[invalid_metadata] object/bad failed spec validation: fields.amount.type: Required' error shape)", - "packages/spec/src/ui/view.zod.ts (container guidance map: type/columns/data/viewKind/filters/sort each name the wrap prescription)", + "packages/runtime/src/http-dispatcher.test.ts#error (the located '[invalid_metadata] object/bad failed spec validation: fields.amount.type: Required' error shape)", + "packages/spec/src/ui/view.zod.ts#viewKind (container guidance map: type/columns/data/viewKind/filters/sort each name the wrap prescription)", "dashboards.strict-widget-rejects-stray-keys (dashboard-kind stray keys — cross-referenced, not duplicated)" ], "history": [ @@ -592,11 +592,11 @@ "traps": ["dispatcher-vs-hono-route"], "automated": { "kind": "unit", "ref": "packages/objectql/src/overlay-precedence.test.ts" }, "source": [ - "packages/spec/src/kernel/metadata-plugin.zod.ts (DEFAULT_METADATA_TYPE_REGISTRY per-kind flags; allowOrgOverride doc: 'runtime returns HTTP 403 not_overridable'; the object/field lock rationale; job's #4509 create lock)", - "packages/objectql/src/overlay-precedence.test.ts ('denied — must throw 403 (not_overridable or not_creatable)')", - "packages/metadata-protocol/src/protocol.ts (isRuntimeCreateAllowed — the write-gate authority)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#DEFAULT_METADATA_TYPE_REGISTRY (DEFAULT_METADATA_TYPE_REGISTRY per-kind flags; allowOrgOverride doc: 'runtime returns HTTP 403 not_overridable'; the object/field lock rationale; job's #4509 create lock)", + "packages/objectql/src/overlay-precedence.test.ts#not_overridable ('denied — must throw 403 (not_overridable or not_creatable)')", + "packages/metadata-protocol/src/protocol.ts#isRuntimeCreateAllowed (isRuntimeCreateAllowed — the write-gate authority)", "ADR-0005 (metadata customization opt-in), ADR-0049 (enforce-or-remove — the job rationale)", - "packages/spec/src/api/protocol.zod.ts (overrideSource registry|env — how GET /meta reports an OS_METADATA_WRITABLE-granted override, the read that made the #7637 correction checkable)", + "packages/spec/src/api/protocol.zod.ts#overrideSource (overrideSource registry|env — how GET /meta reports an OS_METADATA_WRITABLE-granted override, the read that made the #7637 correction checkable)", "objectui packages/app-shell/src/views/metadata-admin/ResourceEditPage.tsx, 1311-1323 (the artifact-backed heuristic behind the read-only banner; cross-linked to objectui#4308)" ], "history": [ @@ -903,12 +903,12 @@ ], "traps": ["hydration-race", "stale-console-bundle", "seed-data-thin", "single-datapoint", "automation-input"], "source": [ - "packages/spec/src/ui/page.zod.ts (PageComponentType enum — namespaced page:/record:/element: block types; PageComponentSchema.dataSource per-element binding; PageComponentSchema.type = union(enum, string) so objectui-registered custom blocks like object-metric/object-chart/object-grid/object-form/object-master-detail-form/flex/grid are valid)", - "examples/app-showcase/src/ui/pages/command-center.page.ts (object-metric bare KPIs + object-chart bar/donut/area on datasets + object-grid work queue), my-work.page.ts ({current_user_id} object-grid + page:card visible-gate), project-workspace.page.ts (object-master-detail-form), active-projects.page.ts (interfaceConfig list: filterBy/sort/appearance/userFilters/addRecord)", + "packages/spec/src/ui/page.zod.ts#PageComponentType (PageComponentType enum — namespaced page:/record:/element: block types; PageComponentSchema.dataSource per-element binding; PageComponentSchema.type = union(enum, string) so objectui-registered custom blocks like object-metric/object-chart/object-grid/object-form/object-master-detail-form/flex/grid are valid)", + "examples/app-showcase/src/ui/pages/command-center.page.ts#chart (object-metric bare KPIs + object-chart bar/donut/area on datasets + object-grid work queue), my-work.page.ts ({current_user_id} object-grid + page:card visible-gate), project-workspace.page.ts (object-master-detail-form), active-projects.page.ts (interfaceConfig list: filterBy/sort/appearance/userFilters/addRecord)", "objectui: packages/components/src/renderers/layout/page.tsx (PageRenderer — region/template dispatch, full-bleed for width:'full' main)", "objectui: packages/react/src/SchemaRenderer.tsx (the OBJUI-001 'Unknown component type' red panel + the SchemaErrorBoundary 'Component failed to render' panel — the NAMED-degradation contract; page./data/user expression scope)", "objectui: e2e/live/showcase-smoke.spec.ts (the shallow render-clean sweep for my-work/project-workspace/active-projects — this item deepens it with block-composition + data-binding assertions)", - "examples/app-showcase/src/ui/datasets/chart-gallery.dataset.ts (showcase_task_metrics / showcase_project_metrics — the chart bindings)" + "examples/app-showcase/src/ui/datasets/chart-gallery.dataset.ts#showcase_task_metrics (showcase_task_metrics / showcase_project_metrics — the chart bindings)" ], "history": [ { "revision": 1, "date": "2026-08-08", "change": "new item: declarative custom pages render their real block composition and bind data (API cross-check, not pixels), with a scratch-page probe proving a bad binding degrades to a NAMED error not a blank — grounded in the four showcase declarative page sources, the PageComponentType enum, objectui PageRenderer + SchemaRenderer, and deepening the shallow showcase-smoke render-clean pin", "ref": "claude/platform-test-checklist-ocwugl" } @@ -995,8 +995,8 @@ ], "traps": ["hydration-race", "stale-console-bundle", "automation-input"], "source": [ - "packages/spec/src/ui/page.zod.ts (PageVariableSchema — `source` is the WRITER component id, read by predicates as page.; PageSchema.variables runtime-live per ADR-0049; PageComponentSchema.visibleWhen binds record/current_user/page., ADR-0089)", - "examples/app-showcase/src/ui/pages/page-variables.page.ts (selectedProjectId ← project_picker; empty-hint vs detail gated on page.selectedProjectId), contact-form.page.ts (four string vars ← text_inputs; submit `api` action posting {{page.}} to /api/v1/forms/contact-us/submit → showcase_inquiry)", + "packages/spec/src/ui/page.zod.ts#PageVariableSchema (PageVariableSchema — `source` is the WRITER component id, read by predicates as page.; PageSchema.variables runtime-live per ADR-0049; PageComponentSchema.visibleWhen binds record/current_user/page., ADR-0089)", + "examples/app-showcase/src/ui/pages/page-variables.page.ts#selectedProjectId (selectedProjectId ← project_picker; empty-hint vs detail gated on page.selectedProjectId), contact-form.page.ts (four string vars ← text_inputs; submit `api` action posting {{page.}} to /api/v1/forms/contact-us/submit → showcase_inquiry)", "objectui: packages/components/src/renderers/layout/page.tsx (PageVariablesProvider mounts the declared variables; PageVariableActionBridge publishes the live snapshot into the action runtime so a submit resolves {{page.}})", "objectui: packages/react/src/SchemaRenderer.tsx (page. threaded into the ExpressionEvaluator scope; visibleWhen/visibility re-evaluated on variable change)", "ADR-0056 (public web-to-lead form → showcase_inquiry), ADR-0089 (visibleWhen canonical predicate)" @@ -1085,10 +1085,10 @@ ], "traps": ["stale-console-bundle", "hydration-race", "single-datapoint", "automation-input"], "source": [ - "packages/spec/src/ui/page.zod.ts (PageSchema.kind = full|slotted|html|react|jsx; html = constrained JSX/HTML+Tailwind compiled by @objectstack/sdui-parser 'parse, never execute' ADR-0080; react = real React executed at render, gated by a host capability defaulting ON, disabled via OS_PAGE_REACT=off, ADR-0081; the superRefine that fails an html/react page with no `source`)", + "packages/spec/src/ui/page.zod.ts#PageSchema (PageSchema.kind = full|slotted|html|react|jsx; html = constrained JSX/HTML+Tailwind compiled by @objectstack/sdui-parser 'parse, never execute' ADR-0080; react = real React executed at render, gated by a host capability defaulting ON, disabled via OS_PAGE_REACT=off, ADR-0081; the superRefine that fails an html/react page with no `source`)", "objectui: packages/components/src/renderers/layout/page.tsx (PageRenderer kind dispatch — compile(src, getJsxManifest()) + SchemaRenderer for html with the 'HTML page failed to compile' panel; ReactKindPage for react; the manifest whitelist is built from the LIVE registry's known types)", "objectui: packages/components/src/renderers/layout/react-page.tsx (ReactKindPage — CAP_REACT_PAGES gate + CapabilityDisabledNotice for OS_PAGE_REACT=off; lazy Sucrase @object-ui/react-runtime transpile; useAdapter/ListView/ObjectForm scope; the 'React page error' fallback)", - "objectui: packages/sdui-parser/src/index.ts (compile(source, manifest) — the pure in-browser parse used at render, no build step)", + "objectui: packages/sdui-parser/src/index.ts#compile (compile(source, manifest) — the pure in-browser parse used at render, no build step)", "examples/app-showcase/src/ui/pages/command-center-jsx.page.ts (kind:'html', static sample KPIs — 'parsed, never executed'), capability-map.page.ts (kind:'html' landing), crm-workbench.page.ts (kind:'react' — useAdapter().find reads the .data envelope, note the #… KPIs-stuck-at-0 fix)", "objectui: content/docs/guide/react-pages.md (react tier: transpiled + evaluated in-app, react-pages capability, OS_PAGE_REACT=off disable)" ], @@ -1242,7 +1242,7 @@ ], "traps": ["stale-console-bundle", "hydration-race", "automation-input", "cache-staleness"], "source": [ - "packages/spec/src/kernel/metadata-plugin.zod.ts (view allowOrgOverride:true), (dashboard allowOrgOverride:true — the overlay door the dashboard leg rides, verified present), (flow allowOrgOverride:false — the same-surface Regime-C negative)", + "packages/spec/src/kernel/metadata-plugin.zod.ts#allowOrgOverride (view allowOrgOverride:true), (dashboard allowOrgOverride:true — the overlay door the dashboard leg rides, verified present), (flow allowOrgOverride:false — the same-surface Regime-C negative)", "docs/adr/0126-packaged-metadata-customization-model.md §3 (Regime O = view, dashboard, report, translation, email_template; flow = Regime C)", "content/docs/capabilities/integrations.mdx + content/docs/build-without-code.mdx (the E1 tier-1 claim under test: 'views and dashboards are yours to edit directly' on installed/packaged apps)", "packages/platform-objects/src/apps/dashboards/system_overview.dashboard.ts (the code-shipped built-in board the dashboard leg edits)", @@ -1349,11 +1349,11 @@ "ref": "packages/metadata-protocol/src/protocol.diagnostics-store-outage.test.ts — pins the outage clause ONLY (503 rethrown, never '0 problems'); the live sweep, scoping parity, refusals, and both browser surfaces still need the run" }, "source": [ - "packages/rest/src/rest-server.ts (the route: registered BEFORE /meta/:type so 'diagnostics' is not captured as a type; severity defaults 'error'; ?type/?severity/?package each single-valued via refuseRepeatedQueryParams #6877; 501 NOT_IMPLEMENTED when the kernel's protocol lacks getMetaDiagnostics; answers res.json(result) BARE)", - "packages/rest/src/rest-route-ledger.ts (REST-only route, bare body = GetMetaDiagnosticsResponseSchema, client meta.getDiagnostics, #12038)", - "packages/metadata-protocol/src/protocol.ts (getMetaDiagnostics: registry-derived type set filtered to registered schemas; reuses the _diagnostics read decoration; stats { count, locked, packages } computed in the same sweep 'so the Studio directory page can render tile counts and a package filter in one round-trip'; 503 outage rethrown #8855 / ADR-0110 D3; 400 unrecognised-spelling rethrown #8924; warnings 'reserved for a future lint layer')", - "packages/metadata-protocol/src/metadata-diagnostics.ts (computeMetadataDiagnostics — errors only, undefined = no opinion for schema-less types; zodIssuesToMetadataIssues names union branches #5598 so Studio has a path to highlight)", - "packages/spec/src/api/protocol.zod.ts (GetMetaDiagnosticsResponseSchema — entries[].diagnostics is the canonical MetadataValidationResultSchema the save path's 422 also speaks)", + "packages/rest/src/rest-server.ts#route (the route: registered BEFORE /meta/:type so 'diagnostics' is not captured as a type; severity defaults 'error'; ?type/?severity/?package each single-valued via refuseRepeatedQueryParams #6877; 501 NOT_IMPLEMENTED when the kernel's protocol lacks getMetaDiagnostics; answers res.json(result) BARE)", + "packages/rest/src/rest-route-ledger.ts#REST (REST-only route, bare body = GetMetaDiagnosticsResponseSchema, client meta.getDiagnostics, #12038)", + "packages/metadata-protocol/src/protocol.ts#getMetaDiagnostics (getMetaDiagnostics: registry-derived type set filtered to registered schemas; reuses the _diagnostics read decoration; stats { count, locked, packages } computed in the same sweep 'so the Studio directory page can render tile counts and a package filter in one round-trip'; 503 outage rethrown #8855 / ADR-0110 D3; 400 unrecognised-spelling rethrown #8924; warnings 'reserved for a future lint layer')", + "packages/metadata-protocol/src/metadata-diagnostics.ts#computeMetadataDiagnostics (computeMetadataDiagnostics — errors only, undefined = no opinion for schema-less types; zodIssuesToMetadataIssues names union branches #5598 so Studio has a path to highlight)", + "packages/spec/src/api/protocol.zod.ts#GetMetaDiagnosticsResponseSchema (GetMetaDiagnosticsResponseSchema — entries[].diagnostics is the canonical MetadataValidationResultSchema the save path's 422 also speaks)", "objectui packages/app-shell/src/views/metadata-admin/DiagnosticsPage.tsx (groups by type descending, per-row deep link..//, severity tabs, the summary badge, the clean Empty state, the loadFailed banner) + useMetadata.ts (useGlobalDiagnostics: strict error count vs warn-only count, counts/locked/packages from stats, older-server catch degrades to empty-not-fatal) + DirectoryPage.tsx,242-254 (package-scoped sweep drives tile badges; diagnostics link only when total > 0) + console/AppContent.tsx,982 (the metadata/_diagnostics route)", "studio-authoring.authoring-validation-not-persisted (why stock stores are clean — the save-time gate; also the known spurious _diagnostics banner on the DESIGNER, which is that item's business, not this sweep's)" ], diff --git a/scripts/check-platform-checklist.mjs b/scripts/check-platform-checklist.mjs index 801d4de438..84b77fa22f 100644 --- a/scripts/check-platform-checklist.mjs +++ b/scripts/check-platform-checklist.mjs @@ -46,6 +46,11 @@ // `use`. A recipe nobody opts into is dead text a runner may still replay. // Both directions together are what the trap vocabulary beside them has // always had — used implies documented, documented implies used. +// - every SYMBOL ANCHOR (`path/to/file.ts#symbolName`) resolves: the cited +// file is in the tree and the cited symbol is still in it, comments masked +// — a symbol surviving only in a docblock is ABSENT. A shrink-never floor +// per family file keeps the population from being emptied one deleted +// `#symbol` at a time (see the symbol-anchor block below); // - and no `call` string — the one field in this ledger a runner REPLAYS — // instructs a `/meta/` URL spelling the boundary merely folds. // `call` ONLY: the fields beside it legitimately quote plural spellings to @@ -726,6 +731,73 @@ let provisioningReachedVerdict = false; let unreferencedReachedVerdict = false; let metaCallReachedVerdict = false; let citationsReachedVerdict = false; +let symbolAnchorsReachedVerdict = false; + +// ── The self-test's own battery roster and floor (#13489, adopted here) ──── +// +// The handshake flags above answer "did this battery reach its verdict?". They +// cannot answer the other half: a battery that reaches its verdict having run +// FEWER assertions than it used to reports a smaller number, and every leg +// below reads the smaller number as a pass. This file already measured that +// exact shape — 141 assertions down to 119, exit 0 — and fixed only the +// early-return half of it. +// +// So the counts are pinned, per battery, as a FLOOR. Adding assertions is +// ordinary work and must not red; a battery BELOW its floor means cases +// stopped running and the remedy is to find what stopped registering. +// +// ⛔ A pinned TOTAL is not the repair: one battery dropping from 20 rows to 3 +// keeps a total "right" the moment a sibling grows. And DELETING an entry +// silences that battery exactly as effectively as zeroing it, so the roster's +// own size is pinned beside it. +// +// The attribution differs from `scripts/pm/ci-failure.mjs`, whose rows open a +// battery by name so a stray assertion lands in a declared bucket: here each +// battery is a FUNCTION returning its own `checked`, so a row cannot be filed +// under the wrong battery in the first place, and the roster's job is the +// floor alone. +const BATTERY_TRAP_VOCABULARY = 'selfTestTrapVocabulary: the trap table, read and refused'; +const BATTERY_PROVISIONING_USE = 'selfTestProvisioningUse: both `use` spellings and all three dangling shapes'; +const BATTERY_UNREFERENCED_RECIPES = 'selfTestUnreferencedRecipes: the reverse direction'; +const BATTERY_META_CALL_SPELLING = 'selfTestMetaCallSpelling: the folded `/meta` plural, read from the live contract'; +const BATTERY_SOURCE_LINE_CITATIONS = 'selfTestSourceLineCitations: the `:NNN` refusal and its silent neighbours'; +const BATTERY_SYMBOL_ANCHORS = 'selfTestSymbolAnchors: the anchor detector, the resolver and the floor'; + +const SELF_TEST_BATTERIES = Object.freeze({ + [BATTERY_TRAP_VOCABULARY]: 22, + [BATTERY_PROVISIONING_USE]: 34, + [BATTERY_UNREFERENCED_RECIPES]: 19, + [BATTERY_META_CALL_SPELLING]: 53, + [BATTERY_SOURCE_LINE_CITATIONS]: 19, + [BATTERY_SYMBOL_ANCHORS]: 29, +}); +const SELF_TEST_BATTERY_FLOOR = 6; + +/** + * @param {Record} ran battery name -> assertions it reported + * @returns {string[]} + */ +function batteryRosterFailures(ran) { + const failures = []; + const declared = Object.keys(SELF_TEST_BATTERIES); + if (declared.length < SELF_TEST_BATTERY_FLOOR) { + failures.push(`the battery roster declares ${declared.length} batteries but the floor is ${SELF_TEST_BATTERY_FLOOR} — deleting an entry silences its floor exactly as effectively as zeroing it`); + } + for (const name of declared) { + if (!(name in ran)) failures.push(`declared battery "${name}" did not run`); + } + for (const name of Object.keys(ran)) { + if (!(name in SELF_TEST_BATTERIES)) failures.push(`battery "${name}" ran but is not declared in SELF_TEST_BATTERIES`); + } + for (const [name, floor] of Object.entries(SELF_TEST_BATTERIES)) { + const n = ran[name]; + if (typeof n === 'number' && n < floor) { + failures.push(`battery "${name}" reported ${n} assertions but its floor is ${floor} — cases stopped running; find what stopped registering (⛔ MAINTAINER-ONLY: lowering a floor is not the repair)`); + } + } + return failures; +} + /** * One wording, ten call sites — five batteries across the two legs that run @@ -1211,8 +1283,17 @@ export const NEIGHBOURING_MAP: Readonly> = Object.freeze( // silent — HTTP status (`status:409`), config literals (`{maxRetries:3}`), // ports (`http://localhost:3000`), clock times (`08:00`, `...T00:00:00Z`) and // JSON quoted in prose (`{"scannedTypes":1}`). Each is pinned below. +// +// A THIRD branch was added by #13788, measured rather than reasoned: sweeping +// the ledger for anchorable citations turned up four line pins the two +// branches above do not reach — `AiChatPage.tsx:~605-615` (the tilde INSIDE +// the colon form, where the battery only ever pinned `~:`) and the `~L7246-7331` +// spelling, which carries no colon at all. Both read exactly like the class +// step (1) removed, and both sat green through it. The `L` form needs its own +// left boundary so an identifier ending in a capital L before digits +// (`SQL2019`) is not read as a line pin. const SOURCE_LINE_CITATION = - /(?:\.(?:ts|tsx|mts|cts|js|mjs|cjs|json|jsonc|md|mdx|ya?ml|sql|css|html|sh|py|toml)|(?} + */ +function findSymbolAnchors(text) { + const out = []; + for (const m of text.matchAll(SYMBOL_ANCHOR)) { + out.push({ file: m[1], ext: m[2].toLowerCase(), symbol: m[3], anchor: m[0] }); + } + return out; +} + +/** + * Which segments of a (possibly dotted) anchor symbol are NOT in the source. + * Every segment must be present: `Foo.bar` naming a member that was dropped is + * the same rot as `Foo` being dropped, and reporting the whole anchor as + * present because its first half survived is the fail-open one level down. + * + * @param {string} source raw file text + * @param {string} symbol the anchor's symbol, dots allowed + * @param {string} ext lower-cased extension of the cited file + * @returns {string[]} + */ +function absentAnchorSegments(source, symbol, ext) { + // JSON has no comment syntax, and masking a `.json` would blank whatever a + // `//`-carrying string value holds — a route literal, a URL. Every other + // anchorable extension is JS-family and goes through the house masker. + const src = ext === 'json' ? source : maskComments(source); + const absent = []; + for (const seg of symbol.split('.')) { + const esc = seg.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + if (!new RegExp(`(?} counts family file -> anchors that resolved + * @param {Record} floors the baseline's `floors` block + * @returns {Array<{file: string, message: string}>} + */ +function anchorFloorProblems(counts, floors) { + const problems = []; + for (const [rel, n] of counts) { + if (n === 0) continue; + if (!(rel in floors)) { + problems.push({ + file: rel, + message: + `${n} symbol anchor(s) resolve here but scripts/checklist-symbol-anchor-baseline.json declares no floor for this file.` + + ' Add the entry at the count measured now — an undeclared file can be emptied of anchors without this gate noticing, which is the dodge the floor exists to close.', + }); + } + } + for (const [rel, floor] of Object.entries(floors)) { + const n = counts.get(rel); + if (n === undefined) { + problems.push({ + file: rel, + message: + `scripts/checklist-symbol-anchor-baseline.json declares a floor of ${floor} for this file, but it is not in the checklist family any more.` + + ' A floor on a file nobody reads is a floor nothing can breach — drop the entry in the same edit that removed the file.', + }); + continue; + } + if (n < floor) { + problems.push({ + file: rel, + message: + `SYMBOL ANCHORS LOST — ${n} anchor(s) resolve here but the floor is ${floor}.` + + ' A citation that gives up its `#symbol` half stops being resolvable by anything, so the count is shrink-never: restore the anchor, or re-author the citation with a symbol this gate can resolve.' + + ' ⛔ MAINTAINER-ONLY: lowering a floor in scripts/checklist-symbol-anchor-baseline.json is a maintainer\'s call, never the author\'s way past a red.', + }); + } + } + return problems; +} + +/** + * Both directions for all three limbs — the detector, the resolver and the + * floor. The detector's whole value is the boundary (this ledger is dense with + * `#`-shaped text that is NOT an anchor), and the resolver's whole value is + * that it can still fire once the ledger is clean, which is precisely when its + * live output goes empty and its green stops meaning anything by itself. + */ +function selfTestSymbolAnchors() { + const failures = []; + let checked = 0; + const t = (what, ok, note = '') => { + checked++; + if (!ok) failures.push(`${what}${note ? ` — ${note}` : ''}`); + }; + const a = (s) => findSymbolAnchors(s); + + // ── the detector FIRES on every spelling an author will write ───────────── + const one = a('see packages/core/src/security/platform-admin.ts#parsePlatformAdminEmails for the refusal'); + t('A1 a bare anchor is found, split into file and symbol', + one.length === 1 && one[0].file === 'packages/core/src/security/platform-admin.ts' && one[0].symbol === 'parsePlatformAdminEmails'); + t('A2 trailing punctuation is not part of the symbol', + a('(packages/rest/src/rest-server.ts#buildRouter).').length === 1 && a('(packages/rest/src/rest-server.ts#buildRouter).')[0].symbol === 'buildRouter'); + const dotted = a('packages/spec/src/data/object.zod.ts#ObjectSchema.shape'); + t('A3 a dotted member anchor keeps both segments', dotted.length === 1 && dotted[0].symbol === 'ObjectSchema.shape'); + t('A4 two anchors in one citation are both found', + a('a/b/x.ts#alpha + a/b/y.tsx#beta').length === 2); + t('A5 every anchorable extension is recognised', + ['ts', 'tsx', 'mts', 'cts', 'js', 'mjs', 'cjs', 'json'].every((e) => a(`p/q/f.${e}#sym`).length === 1)); + t('A6 the extension is reported lower-cased, so the anchorable test cannot be dodged by case', a('p/q/F.TS#sym')[0]?.ext === 'ts'); + + // ── and STAYS SILENT on the `#` shapes this ledger is full of ───────────── + t('S1 a cross-repo issue reference is not an anchor', a('the objectui#2563 regression').length === 0); + t('S2 a bare issue reference is not an anchor', a('closed by #13786 and #13482').length === 0); + t('S3 a JSON-pointer URL fragment is not an anchor', a('https://example.com/schema.json#/$defs/Item').length === 0); + t('S4 an ADR section reference is not an anchor', a('ADR-0025 §3.3, ADR-0090 D4').length === 0); + t('S5 a BARE citation — the shape most of this ledger still carries — is not an anchor', + a('packages/objectql/src/search-filter.ts (each latin term ORs $contains)').length === 0); + t('S6 a dotted machine name with no path is not an anchor', a('sys_user.name and showcase.export_data').length === 0); + t('S7 an anchor-shaped tail on a word with no slash is not an anchor', a('release-15.1#B2').length === 0); + t('S8 an email-ish `@`-prefixed path is not read as an anchor', a('@objectstack/spec/src/x.ts#Y').length === 0); + + // ── the resolver FIRES ──────────────────────────────────────────────────── + const src = [ + '// parsePlatformAdminEmails used to live here; renamed in #9999.', + "export const RESOLVED = { mode: 'strict' };", + "const ROUTE = '/api/v1/meta/object';", + 'export function hasPlatformAdminStanding(x) { return RESOLVED.mode === x; }', + ].join('\n'); + t('R1 a symbol that is gone from the file is reported absent', + absentAnchorSegments(src, 'parsePlatformAdminEmails', 'ts').length === 1); + t('R2 a symbol surviving ONLY in a comment is ABSENT — prose about a symbol is not a symbol', + absentAnchorSegments(src, 'renamed', 'ts').length === 1); + t('R3 a dotted anchor whose SECOND segment is gone is reported, not passed on the first', + absentAnchorSegments(src, 'RESOLVED.strictness', 'ts').join(',') === 'strictness'); + t('R4 a substring of a present identifier does not count as present', + absentAnchorSegments(src, 'PlatformAdmin', 'ts').length === 1); + + // ── and STAYS SILENT ────────────────────────────────────────────────────── + t('P1 a declared export resolves', absentAnchorSegments(src, 'hasPlatformAdminStanding', 'ts').length === 0); + t('P2 a const resolves', absentAnchorSegments(src, 'RESOLVED', 'ts').length === 0); + t('P3 both segments of a live dotted anchor resolve', absentAnchorSegments(src, 'RESOLVED.mode', 'ts').length === 0); + t('P4 a symbol inside a STRING literal counts as present — this ledger cites machine names, not only exports', + absentAnchorSegments(src, 'strict', 'ts').length === 0); + t('P5 `.json` is scanned unmasked, so a `//`-carrying string value is never blanked away', + absentAnchorSegments('{ "route": "https://x/y", "kind": "live" }', 'live', 'json').length === 0); + + // ── the floor, both directions ──────────────────────────────────────────── + const floors = { 'areas/a.json': 10, 'areas/b.json': 4 }; + t('B1 a file below its floor is reported', + anchorFloorProblems(new Map([['areas/a.json', 9], ['areas/b.json', 4]]), floors).length === 1); + t('B2 the report names the shrink-never rule and the authority for lowering a floor', + anchorFloorProblems(new Map([['areas/a.json', 0], ['areas/b.json', 4]]), floors)[0]?.message.includes('⛔ MAINTAINER-ONLY')); + t('B3 a file carrying anchors with no floor entry is reported — an undeclared file could be emptied silently', + anchorFloorProblems(new Map([['areas/a.json', 10], ['areas/b.json', 4], ['areas/c.json', 3]]), floors).length === 1); + t('B4 a floor whose file left the family is reported', + anchorFloorProblems(new Map([['areas/a.json', 10]]), floors).length === 1); + t('B5 counts ABOVE the floor are silent — adding anchors is ordinary work', + anchorFloorProblems(new Map([['areas/a.json', 99], ['areas/b.json', 4]]), floors).length === 0); + t('B6 a family file with zero anchors needs no entry — a document that cites nothing is not a regression', + anchorFloorProblems(new Map([['areas/a.json', 10], ['areas/b.json', 4], ['RUNNER.md', 0]]), floors).length === 0); + + symbolAnchorsReachedVerdict = true; + return { checked, failures }; +} + if (process.argv.slice(2).includes('--self-test')) { const trap = selfTestTrapVocabulary(); const prov = selfTestProvisioningUse(); const unref = selfTestUnreferencedRecipes(); const metaCall = selfTestMetaCallSpelling(); const cites = selfTestSourceLineCitations(); + const anchors = selfTestSymbolAnchors(); requireReachedVerdict('selfTestTrapVocabulary', trapReachedVerdict); requireReachedVerdict('selfTestProvisioningUse', provisioningReachedVerdict); requireReachedVerdict('selfTestUnreferencedRecipes', unreferencedReachedVerdict); requireReachedVerdict('selfTestMetaCallSpelling', metaCallReachedVerdict); requireReachedVerdict('selfTestSourceLineCitations', citationsReachedVerdict); - const failures = [...trap.failures, ...prov.failures, ...unref.failures, ...metaCall.failures, ...cites.failures]; + requireReachedVerdict('selfTestSymbolAnchors', symbolAnchorsReachedVerdict); + const rosterFailures = batteryRosterFailures({ + [BATTERY_TRAP_VOCABULARY]: trap.checked, + [BATTERY_PROVISIONING_USE]: prov.checked, + [BATTERY_UNREFERENCED_RECIPES]: unref.checked, + [BATTERY_META_CALL_SPELLING]: metaCall.checked, + [BATTERY_SOURCE_LINE_CITATIONS]: cites.checked, + [BATTERY_SYMBOL_ANCHORS]: anchors.checked, + }); + const failures = [...trap.failures, ...prov.failures, ...unref.failures, ...metaCall.failures, ...cites.failures, ...anchors.failures, ...rosterFailures]; if (failures.length === 0) { console.log( - `✓ check-platform-checklist --self-test: ${trap.checked + prov.checked + unref.checked + metaCall.checked + cites.checked} assertions — the trap-table extractor reads a good table and REFUSES an empty/renamed/reshaped one;` + + `✓ check-platform-checklist --self-test: ${trap.checked + prov.checked + unref.checked + metaCall.checked + cites.checked + anchors.checked} assertions — the trap-table extractor reads a good table and REFUSES an empty/renamed/reshaped one;` + ' `fixtures.provisioning.use` resolves both spellings (own-area key and `:`) and fires on all three dangling shapes;' + ' the unreferenced-recipe direction fires on a recipe nobody uses while leaving a cross-area consumer, a retired consumer and a `$`-annotation alone;' + ' and the `/meta` call-spelling refusal reads its vocabulary out of the live generated contract, fires on every folded spelling a `call` can instruct, and stays silent on the canonical singular, on parameter placeholders, and on the `why`/`expect`/`source`/`requires` prose that narrates the fold;' + - ' and the source-line-citation refusal fires on every spelling this ledger carried (file-anchored, range, bare continuation, parenthesised, `~:`, comma/slash-chained) while staying silent on HTTP status, config literals, URL ports, clock times, JSON quoted in prose and the README placeholder that documents the ban.', + ' and the source-line-citation refusal fires on every spelling this ledger carried (file-anchored, range, bare continuation, parenthesised, `~:`, comma/slash-chained) while staying silent on HTTP status, config literals, URL ports, clock times, JSON quoted in prose and the README placeholder that documents the ban;' + + ' and the symbol-anchor resolver finds every anchor spelling an author writes while reading no issue reference, URL fragment, ADR section or bare citation as one, reports a symbol that survives only in a comment as ABSENT, resolves both segments of a dotted anchor, and holds the per-file anchor floor in both directions.', ); process.exit(0); } @@ -1348,6 +1689,32 @@ if (citationControl.failures.length) { process.exit(1); } +// And for the symbol-anchor resolver. Same reasoning as the two above, plus +// one this file has not had before: this check's subject population is +// AUTHORED, so it can be emptied. Its control proves the detector still finds +// anchors and the resolver still refuses an absent symbol; the floor beside it +// proves the ledger still carries anchors for them to be found in. +const symbolAnchorControl = selfTestSymbolAnchors(); +requireReachedVerdict('selfTestSymbolAnchors', symbolAnchorsReachedVerdict); +if (symbolAnchorControl.failures.length) { + console.error("check-platform-checklist: the symbol-anchor resolver's own positive control FAILED — an anchor naming a symbol its file no longer contains would pass unreported, which is the rot this resolver was added to end.\n"); + for (const f of symbolAnchorControl.failures) console.error(` ✗ ${f}`); + process.exit(1); +} +const inlineRosterFailures = batteryRosterFailures({ + [BATTERY_TRAP_VOCABULARY]: trapControl.checked, + [BATTERY_PROVISIONING_USE]: provisioningControl.checked, + [BATTERY_UNREFERENCED_RECIPES]: unreferencedControl.checked, + [BATTERY_META_CALL_SPELLING]: metaCallControl.checked, + [BATTERY_SOURCE_LINE_CITATIONS]: citationControl.checked, + [BATTERY_SYMBOL_ANCHORS]: symbolAnchorControl.checked, +}); +if (inlineRosterFailures.length) { + console.error('check-platform-checklist: the self-test battery roster FAILED — assertions stopped running, and every leg below would read the smaller count as a pass.\n'); + for (const f of inlineRosterFailures) console.error(` ✗ ${f}`); + process.exit(1); +} + // The folded-spelling vocabulary, read from the contract before anything is // judged against it. Refused rather than defaulted: with no vocabulary every // `call` validates against an empty set and this gate prints the same green it @@ -1702,6 +2069,79 @@ for (const rel of familyFiles(CHECKLIST_DIR)) { } } +// ── The symbol-anchor sweep, over the same family the citation sweep reads ── +// Cached per cited file: the ledger points many citations at the same hot +// sources, and re-reading + re-masking each one per anchor is the difference +// between a gate that costs milliseconds and one nobody runs. +const anchorSourceCache = new Map(); +function anchorSource(absPath) { + if (!anchorSourceCache.has(absPath)) anchorSourceCache.set(absPath, readFileSync(absPath, 'utf8')); + return anchorSourceCache.get(absPath); +} + +const anchorCounts = new Map(); +let anchorsScanned = 0; +let anchorsResolved = 0; +for (const rel of familyFiles(CHECKLIST_DIR)) { + const text = readFileSync(join(CHECKLIST_DIR, rel), 'utf8'); + let resolved = 0; + for (const anchor of findSymbolAnchors(text)) { + anchorsScanned++; + if (!ANCHORABLE_EXTENSIONS.has(anchor.ext)) { + err(rel, null, `UNRESOLVABLE ANCHOR — \`${anchor.anchor}\`: this gate reads symbols out of ${[...ANCHORABLE_EXTENSIONS].join('/')} only, so a \`#symbol\` on a .${anchor.ext} resolves nowhere and would read as verified while pointing at nothing. Cite that file BARE (path, no \`#\`) and put the symbol in the prose beside it.`); + continue; + } + const abs = join(ROOT, anchor.file); + if (!existsSync(abs)) { + err(rel, null, `ANCHOR FILE NOT FOUND — \`${anchor.anchor}\`: ${anchor.file} is not in this repo. A path this gate cannot open is not an anchor; a sibling-repo citation (objectui/cloud) stays BARE, and a moved file needs the pin re-pointed.`); + continue; + } + const absent = absentAnchorSegments(anchorSource(abs), anchor.symbol, anchor.ext); + if (absent.length) { + err(rel, null, `ABSENT SYMBOL — \`${anchor.anchor}\`: ${absent.map((s) => `\`${s}\``).join(' and ')} ${absent.length > 1 ? 'are' : 'is'} not in ${anchor.file} outside its comments. The symbol was renamed or removed and the citation kept reading as "verified against source" — re-point it at what the file carries now, or drop the \`#symbol\` half and cite the file bare.`); + continue; + } + resolved++; + anchorsResolved++; + } + anchorCounts.set(rel, resolved); +} + +// The census mode: how the baseline beside this gate is authored, and the one +// answer to "what would the floor be if I measured it now". Read-only and +// explicitly NOT a verdict — a gate that regenerated its own ratchet would +// edit the tree and report nothing (the house rule the spec wrapper states). +if (process.argv.slice(2).includes('--anchor-census')) { + const census = {}; + for (const [rel, n] of [...anchorCounts].sort(([a], [b]) => a.localeCompare(b))) if (n > 0) census[rel] = n; + console.log(JSON.stringify(census, null, 2)); + console.error(`\ncheck-platform-checklist --anchor-census: ${anchorsResolved}/${anchorsScanned} anchors resolved across ${Object.keys(census).length} family files. This is a CENSUS, not a verdict — run the gate with no flags for that.`); + process.exit(0); +} + +// The floor. Read as a REFUSAL when it is missing: with no baseline every +// count validates against nothing and this limb prints the same green it +// printed before it existed. +if (!existsSync(ANCHOR_FLOOR_FILE)) { + console.error('check-platform-checklist: missing scripts/checklist-symbol-anchor-baseline.json — the per-file symbol-anchor floor.'); + console.error('\nThis is a REFUSAL, not a pass: with no floor, a ledger emptied of every anchor would validate against nothing and report zero problems.'); + process.exit(1); +} +let anchorFloors; +try { + const baseline = JSON.parse(readFileSync(ANCHOR_FLOOR_FILE, 'utf8')); + anchorFloors = baseline?.floors; + if (!anchorFloors || typeof anchorFloors !== 'object' || Array.isArray(anchorFloors)) throw new Error('no "floors" object'); + if (Object.keys(anchorFloors).length < ANCHOR_FLOOR_ROSTER_FLOOR) { + throw new Error(`"floors" declares ${Object.keys(anchorFloors).length} files but the roster floor is ${ANCHOR_FLOOR_ROSTER_FLOOR}`); + } +} catch (e) { + console.error(`check-platform-checklist: cannot read the symbol-anchor floor — ${e.message}`); + console.error('\nThis is a REFUSAL, not a pass: an unreadable or gutted floor is indistinguishable from a ledger that legitimately shrank.'); + process.exit(1); +} +for (const problem of anchorFloorProblems(anchorCounts, anchorFloors)) err(problem.file, null, problem.message); + if (errors.length) { console.error(`check-platform-checklist: ${errors.length} problem(s)\n`); for (const e of errors) console.error(` ✗ ${e}`); @@ -1722,5 +2162,6 @@ console.log( ` provisioning: ${recipeTotal} area recipes, ${recipeRefs} item references resolved (${qualifiedRefs} area-qualified), ${recipesReferenced}/${recipeTotal} recipes referenced;` + ` meta-URL spelling: ${metaCallsScanned} \`call\` strings scanned against ${FOLDED_META_SPELLINGS.size} folded spellings;` + ` source citations: ${citationsScanned} family files carry no \`file:line\` pin;` + - ` (self-checks: ${trapControl.checked} trap-vocabulary + ${provisioningControl.checked} provisioning-resolve + ${unreferencedControl.checked} unreferenced-recipe + ${metaCallControl.checked} meta-call-spelling + ${citationControl.checked} source-line-citation assertions).`, + ` symbol anchors: ${anchorsResolved}/${anchorsScanned} resolved against ${anchorSourceCache.size} cited sources, ${Object.keys(anchorFloors).length} file floors held;` + + ` (self-checks: ${trapControl.checked} trap-vocabulary + ${provisioningControl.checked} provisioning-resolve + ${unreferencedControl.checked} unreferenced-recipe + ${metaCallControl.checked} meta-call-spelling + ${citationControl.checked} source-line-citation + ${symbolAnchorControl.checked} symbol-anchor assertions).`, ); diff --git a/scripts/checklist-symbol-anchor-baseline.json b/scripts/checklist-symbol-anchor-baseline.json new file mode 100644 index 0000000000..334c369ada --- /dev/null +++ b/scripts/checklist-symbol-anchor-baseline.json @@ -0,0 +1,22 @@ +{ + "$comment": "Symbol-anchor floor for docs/qa/platform-checklist/ — the shrink-never population of `path/to/file.ts#symbol` citations that scripts/check-platform-checklist.mjs resolves against real source. Authored from `node scripts/check-platform-checklist.mjs --anchor-census` (#13788, step (2) of #13482's ruling). Each entry is the count of anchors that RESOLVED in that family file; adding anchors is ordinary work and never reds. A count that falls below its floor means citations gave up the one half this gate can read, which is the silent remedy every red here would otherwise have.", + "$authority": "⛔ MAINTAINER-ONLY: lowering a floor is a maintainer's call. Retiring items legitimately removes anchors — that is the case a maintainer rules on, per instance, and never the author's way past a red.", + "floors": { + "README.md": 1, + "areas/access-security.json": 44, + "areas/ai.json": 18, + "areas/api-backend.json": 41, + "areas/approvals.json": 37, + "areas/attachments-storage.json": 28, + "areas/automation.json": 51, + "areas/cli.json": 42, + "areas/dashboards.json": 22, + "areas/i18n.json": 11, + "areas/identity-auth.json": 82, + "areas/integration-system.json": 57, + "areas/platform-core.json": 58, + "areas/records-forms.json": 78, + "areas/search.json": 14, + "areas/studio-authoring.json": 26 + } +} diff --git a/scripts/pm/dispatch-gates.mjs b/scripts/pm/dispatch-gates.mjs index 36500aff75..4c6d70c13d 100644 --- a/scripts/pm/dispatch-gates.mjs +++ b/scripts/pm/dispatch-gates.mjs @@ -2939,6 +2939,7 @@ const COMPOUND_ANCHOR_LEDGER = [ ['scripts/check-platform-checklist.mjs', 'selfTestUnreferencedRecipes', false], ['scripts/check-platform-checklist.mjs', 'selfTestMetaCallSpelling', false], ['scripts/check-platform-checklist.mjs', 'selfTestSourceLineCitations', false], + ['scripts/check-platform-checklist.mjs', 'selfTestSymbolAnchors', false], ['scripts/check-regen-pending.mjs', 'fixtureSelfTest', false], ['scripts/check-regen-pending.mjs', 'prePushIsArmedSelfTest', false], ['scripts/check-regen-pending.mjs', 'decisionTableSelfTest', false],