Skip to content

QA run · release-18.3.0-w4-l1 (40/40) · 7fa9873f · 2026-11-02 · 37 PASS / 2 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN #29124

Open

Description

@objectstack-fleet

domain:devx#1 · os-warren · session_01JQwxbZx1yZXiTrFXKbSdFZ

Part of #27377 (wave 4, Tier 1 live legs, card W4-L1)

Selector release-18.3.0-w4-l1: the live legs of the 40 Tier 1 items in access-security, ai, api-backend, approvals, attachments-storage, automation and cli whose pins record #28929 (W4-C2a, same subject) left a clause partial, skipped or blocked. Release-candidate rerun (SKILL §5 trigger 1), approved by the maintainer on 2026-11-02 07:06Z. Every item has a verdict: 37 PASS / 2 PARTIAL / 1 FAIL / 0 BLOCKED / 0 NOT-RUN. No item is planned.

Coverage mode: this card drove the live legs, not the pins. Its targets were the 137 clauses #28929 scored below pass (88 partial, 43 skipped, 6 blocked). Each was judged live on a booted stack (HTTP, CLI or browser, as the clause asks). The 63 clauses #28929 scored pass keep that verdict and were not re-run. Item verdict = the #28929 verdicts plus the live verdicts here (RUNNER.md "Verdicts"). Of the 137 targets: 125 pass, 6 fail, 6 blocked, 0 skipped.

Environment fingerprint

  • Subject: framework 7fa9873f38f7a0158e3ce17338dcc0f446b113e3 (detached, never followed main or the Version PR chore: version packages #26517). ui/ is in the same tree; the console was built from it.
  • Build: pnpm install --frozen-lockfile (37 s), pnpm exec turbo run build --concurrency=2: 118/118 tasks, 0 cached, 17m21s; pnpm console:build: 72/72 (71 cached), packages/console/dist ready. check-dev-prereqs: 106 package artifacts present.
  • Node 22, pnpm 10.31.0, Playwright chromium from /opt/pw-browsers. 4 CPUs, ~15 GB. This container holds one address, 192.0.2.2 (TEST-NET-1).
  • Boots: every boot ran from a scratch copy of examples/app-showcase (tar copy, node_modules symlinked) so no install, upload or datasource file escaped into the shared tree, with its own port (41611–41674) and file DB:
    OS_PORT=P OS_STORAGE_LOCAL_ROOT=… OS_HOME=… setsid node packages/cli/bin/run.js dev --ui --seed-admin --no-compile --no-watch -p P -d file:…/data.db.
    Variants: a walled group boot (OS_TENANCY_POSTURE=group, OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai, OS_AUTH_MEMBERSHIP_POLICY=invite-only; /auth/config read tenancyPosture "group"); scheduled work on (OS_AUTOMATION_SCHEDULED_WORK_ENABLED=1) beside a default boot; SIGTERM then cold boot over the same DB for the restart legs; OS_MCP_SERVER_ENABLED=false; four MCP OAuth host boots (unheld 10.0.0.5, held 192.0.2.2 with and without OS_ALLOW_INSECURE_OAUTH_HTTP, https via --cert/--key); a wall-clock offset shim for the SLA leg; NODE_ENV=production os serve for the automigrate warning; a local verdaccio 6.11.1 carrying the subject's 74 packed @objectstack/* packages for the scaffold's pre-release leg.
  • Browser legs: the boot's own /_console, form sign-in (cookie + bearer), at most three Chromium sessions at once card-wide.
  • Seed: --seed-admin (admin@objectos.ai), the showcase demo personas, and members minted by admin create-user + change-password; runtime fixtures in scratch packages are named in their rows.
  • Times: 2026-11-02 07:13Z (checkout) to 07:59Z (last verdict).
  • Execution: the card session built the tree and dispatched seven runner subagents (effort high, one area group each, own ports and DBs); it checked each runner's evidence against its rows before writing this record.

Scope: selector and the revision each verdict is valid for

  • access-security (5): fls-mask-and-strip r8, grant-table-write-contract r8, me-permissions-aggregation-parity r4, record-access-explain r4, share-link-landing-page r6
  • ai (5): mcp-coded-refusals-and-runtime-exposure r4, mcp-http-surface r8, mcp-oauth-private-host-transport r6, mcp-validate-expression r6, skill-instructions-mcp-prompts r4
  • api-backend (2): action-activation-door-contract r7, bulk-write-contract r6
  • approvals (4): act-door-and-admin-rescue-slate r5, email-action-token-door r10, request-page-reply-and-reader-visibility r5, status-mirror-field r6
  • attachments-storage (9): attach-requires-parent-edit r9, download-authz-both-sides r10, inline-grid-receipt-cells r5, orphan-tombstone-reap r4, presigned-upload-roundtrip r3, read-inherits-parent-rls r9, sys-file-status-pipeline r5, upload-limits-scope-and-ownership r4, upload-session-abort r8
  • automation (10): durable-suspend-restart r4, flow-authoring-refusals-v18 r5, flow-registration-verdict-on-read r2, flow-run-semantics-v18 r3, inbound-hook-timestamped-signature r2, paused-run-trigger-record-masked r5, scheduled-work-opt-in r2, screen-flow-roundtrip r7, setup-packaged-automation-board r10, trigger-type-matrix r10
  • cli (5): dev-automigrate-policy r8, migrate-meta-write-stored-and-all r3, scaffold-first-run r7, validate-v18-authoring-verdicts r2, verify-derived-proof-families r3

Reproduction rules

1. automation.setup-packaged-automation-board · A0, A1, A2, A3, A5, A6 · FAIL (stale spec)

The item still describes the Setup › Packaged automation page whose flows half #28785 retired (merged 2026-11-01; ADR-0146 D11 retires the page's flows half, D4 moves the flow switch to the Studio Automations pillar and clone to Studio's New similar).

  1. Boot the stock showcase; sign in as admin. GET /api/v1/meta/app?id=setup.
    • Expected (A0): group_apps holds {id: nav_packaged_automation, type: component, componentRef: automation:packaged}.
    • Actual: it holds {id: nav_packaged_actions, componentRef: automation:packaged-actions, label: Packaged Actions}; nav_packaged_automation occurs 0 times.
  2. Open the Setup sidebar in the console. It shows "Packaged Actions" and no "Packaged Automation"; the link lands on /_console/apps/com.objectstack.setup/component/automation/packaged-actions (heading "Packaged actions"), which lists no packaged-flow-* rows, no flow switch and no flow Clone (A1, A2, A3's flow half, A5's toggle cells, A6).
  3. Type /_console/apps/com.objectstack.setup/component/automation/packaged: it redirects to /_console/studio/com.example.showcase/metadata/flow?package=com.example.showcase.
  • The replacement surfaces, measured live: Studio auto-ledger-switch toggles a flow and the state persists across a reload and in the activation row (what A2 asks); Studio auto-new-similar-* opens the dialog A6 asks for, validates name and label, and shows the 400 sentence in role=alert; the Studio rail and GET /api/v1/automation/_status both list 32 flows (A1); the Packaged actions page carries the actions half with no clone (A3); a 409 on the switch shows verbatim in Studio's refusal strip, which has no role=alert (A5). A4 and A7 pass live.
  • Reproduced twice: two browser runs of the Setup nav and page and two of the Studio replacement, each on a fresh form sign-in, plus two API reads of the nav. Self-check: testid/role targeting, screenshot before DOM reads, member cells in their own context with get-session confirming the identity, console built from this tree (the redirect matches ui/ at the subject).
  • Disposition: stale spec. The re-point is already carried by QA checklist: rewrite the Setup-designer items to the Studio door and add the .custom customization journey, with one live e2e through Studio #28579 (open, ADR-0146 rollout N-29), whose list names this item; no new card.

Derived item verdicts

  • PASS (37): every item not listed below.
  • PARTIAL (2):
    • api-backend.action-activation-door-contract: A4 blocked(fixture, knownGaps[3]). The metadata-plane 503 arm fires only when resolveRouteActionDeclaration catches a throw from the metadata plane, and no stock request reaches it; the 404 and zero-ledger-row halves of A4 held live.
    • approvals.email-action-token-door: A0, A1, A3, A4, A5 blocked(fixture, knownGaps[0]). Re-confirmed at the subject: issueActionTokens has no production caller, so no approver email or notification mints an action token. The live at-rest reminder read and the dead-link page held.
  • FAIL (1): automation.setup-packaged-automation-board (stale spec, rule 1 above).

Delta against the pins record (#28929, same subject)

Delta against wave 3 (#27409, at c4c515a)

  • PASS→PASS: 29 · PARTIAL→PASS: 9 · PARTIAL→PARTIAL: 2 · PARTIAL→FAIL: 1 · PASS→FAIL: 0 · PASS→PARTIAL: 0.
  • PARTIAL→PASS (9): ai.mcp-oauth-private-host-transport (r6 stages A0 with an unheld literal), approvals.status-mirror-field (SLA cascade driven with a clock shim), approvals.request-page-reply-and-reader-visibility (reader grant and the reader flag staged live in a scratch package), attachments-storage.read-inherits-parent-rls (bounded probe reached with a >2000-row federated object), attachments-storage.sys-file-status-pipeline, attachments-storage.orphan-tombstone-reap and attachments-storage.upload-session-abort (failure legs staged live: behind-hook insert, EISDIR byte path, immutable parts dir, removed part file), automation.paused-run-trigger-record-masked (runtime object with an internal: true field and a member persona).
  • PARTIAL→PARTIAL (2): api-backend.action-activation-door-contract and approvals.email-action-token-door, same gaps as wave 3.
  • PARTIAL→FAIL (1): automation.setup-packaged-automation-board. Wave 3 judged the page before feat(setup): retire Packaged automation's flows half; Packaged actions is its own Setup entry #28785 retired it; this is the retirement, not a regression.

Off-clause probes (RUNNER rule 8)

About 40 agent-minutes in all, after the clauses had verdicts, on the same boots and personas (admin, demo personas, minted members, auditor, anonymous; walled org admin and members).

  • access-security (~6 min): security/explain with unknown/empty ids and cross-user callers; FLS filter/orderby/fields on masked keys; /me trio with empty userId=, trailing slash, garbage bearer; walled grant-table writes naming another org, unknown or empty user and set ids; share-link tokens (empty, whitespace, suffixed, upper-cased), ineligible records, unknown audiences, another member's links. Finding: a share link's redactFields accepts a field the object does not have (extracted).
  • ai (~5 min): MCP tools with missing, unknown, empty and wrong-typed arguments as admin, auditor, anonymous and a bogus key; prompts/get; /mcp/skill variants. Finding: a malformed params answers JSON-RPC -32603 "Internal error" carrying a raw Zod issue array instead of -32602 (extracted).
  • api-backend + approvals (~6 min): createMany/updateMany/deleteMany boundary payloads, owner spoofing, /batch unknown ops; the act page with dead token shapes; reply door; activation door shapes. Finding: deleteMany {"ids":[""]} refuses with the sentence "requires 'ids' to be an array" and an empty fields[] (extracted).
  • attachments-storage (~5 min): unknown/empty ids on every storage door, presign size/name edges, oversize PUT then complete, acl flips by non-owners, admin completing a member's upload. No finding.
  • automation API (~4 min): run reads and resume by readers outside the scope, unknown and mismatched run/flow names, runs limit boundary, hook door bodies and clock skew. No finding.
  • automation browser/CLI (~9 min): screen and resume doors with unknown runs, extra keys, blank values, terminal runs, cross-flow names, member and anonymous; PUT /meta/flow name mismatch, bad names, empty body. Findings: a text field stores an object or array as its JSON text (extracted); Studio's refusal strip has no role=alert, so a refused flow switch is not announced (extracted).
  • cli (~5 min): invalid autoMigrate values, empty verify families, invalid scaffold names, a non-empty current directory, migrate meta --stored over an absent database. Findings: a current-directory scaffold overwrites the user's existing files (extracted); os migrate meta --stored --json over an absent database answers clean: true (added to cli: os migrate meta --json answers an empty (--from 18) or inverted (--from 19) range like a clean replay; the "replayed nothing" warning exists only on the human path #28947, same class, different door).
  • No security finding.

Time: environment prep vs testing

  • Environment prep: checkout + install + full build + console build 07:13Z–07:33Z (about 20 min wall), plus about 35 agent-minutes of per-runner scratch copies, boots, personas and fixtures (incl. ~6 min verdaccio pre-release registry).
  • Testing: seven runners 07:33Z–07:59Z (26 min wall), about 75 agent-minutes of clause driving plus about 40 of probes.
  • Ratio about 1 to 2. Wall clock to the last verdict about 46 min.

Per-clause verdicts

One row per TARGET clause (the clauses #28929 left below pass), with the layer and one line of text evidence. The tables are in the two comments below. The #28929 pass rows are not repeated.

Checklist-accuracy findings, fixture gaps and pin-promotion candidates from this run are parked on #27377 (anchor comment), not extracted.

Activity

  1. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbZx1yZXiTrFXKbSdFZ

    Per-clause verdicts (1/2): access-security, ai, api-backend, approvals. Live legs only; the #28929 pass rows keep their verdict and are not repeated.

    access-security

    item · rev clause verdict layer · text evidence
    access-security.fls-mask-and-strip · r8 A3 pass browser-live (/_console, form-signed-in contributor C holding showcase_contributor): …/showcase_project/record/KRfrBH2I0Yqyh2MU/edit rendered (screenshot): input[name=budget] disabled=true value 90000, input[name=spent] disabled=true value 88000, each with "You do not have edit access to this field."; name/owner/start_date/end_date enabled. Admin contrast on the same form: budget and spent disabled=false. negative[0] held at the server: C's direct budget PATCH 403 "[Security] Field write denied … [budget]", re-read 90000
    access-security.fls-mask-and-strip · r8 A4 pass HTTP-booted-server + browser-live: fresh member L holding only the STOCK showcase_client_liaison (+ baselines, no showcase_contributor): GET /api/v1/data/showcase_project/KRfrBH2I0Yqyh2MU 200 with budget, spent and budget_remaining keys ABSENT (not null); list 200, 0 of 6 rows carry any of the three keys; admin GET same row budget 90000, spent 88000, budget_remaining 2000. UI: L's detail page shows no Budget highlight and no Financials section, no 90,000/88,000/2,000 anywhere; its POST …/page network body carries the record with all three keys absent. Admin's detail shows Budget $90,000.00 and Spent $88,000.00 and its …/page record carries all three. UI and API agree for both personas (budget_remaining is not on the detail layout for either)
    access-security.grant-table-write-contract · r8 A4 pass HTTP-booted-server (walled boot, OS_TENANCY_POSTURE=group, /auth/config tenancyPosture "group"): org A and org B created via /auth/organization/create, OA promoted to owner of A (sets organization_admin), NB/NB2 members of B only. OA in active org A: POST /api/v1/data/sys_user_permission_set {user_id: NB, permission_set: showcase_contributor} → 400 VALIDATION_FAILED fields[0] {field: user_id, code: reference_not_found, constraint.target sys_member}; NB2 same 400; controls: MA (member of A) 201, OA self 201; sys_user_permission_set rows for NB = 0
    access-security.me-permissions-aggregation-parity · r4 A0 pass HTTP-booted-server: C map objects.showcase_project allowEdit:true → C PATCH name 200, admin re-read shows the new name; P map allowEdit:false → P identical PATCH 403 PERMISSION_DENIED, name unchanged. Fresh sign-in per persona; userId in each map = that persona's session user
    access-security.me-permissions-aggregation-parity · r4 A1 pass HTTP-booted-server: C fields['showcase_project.budget'] {readable:true, editable:false} (spent and budget_remaining the same); C budget PATCH 999999999 → 403 "[Security] Field write denied … [budget]", admin re-read 90000 unchanged; C GET 200 carries budget 90000
    access-security.me-permissions-aggregation-parity · r4 A2 pass HTTP-booted-server: registered apps showcase_app[req []], setup[req setup.access], account[req []]. /me/apps admin = [showcase_app, setup, account] (systemPermissions include setup.access); P = [showcase_app, account] (systemPermissions []). C, A and L match P. W = [showcase_app, setup, account] (holds setup.access). 0 returned apps violate requiredPermissions ⊆ systemPermissions or tabPermissions hidden, across admin/P/C/W/A/L
    access-security.me-permissions-aggregation-parity · r4 A3 pass HTTP-booted-server (full stack, auth gate mounted), no Authorization header: GET /api/v1/auth/me/permissions 200 {"authenticated":false}; /api/v1/auth/me/localization 200 {"authenticated":false}; /api/v1/me/apps 200 {"apps":[]}. A garbage bearer also gets 200 {"authenticated":false} on permissions
    access-security.me-permissions-aggregation-parity · r4 A4 pass HTTP-booted-server: P GET /auth/me/permissions?userId=‹C id› → userId = P's own id, body byte-identical to P's un-parameterised body, no showcase_contributor in permissionSets; ?userId= on /auth/me/localization and /me/apps also byte-identical to P's own answers
    access-security.me-permissions-aggregation-parity · r4 A5 pass HTTP-booted-server: P /auth/me/localization 200 {authenticated:true, currency:null, locale:"en-US", timezone:"UTC"} (P's GET /api/settings/localization 403 SETTINGS_FORBIDDEN, so the settings surface is setup-gated and the resolved answer is not); admin PUT /api/settings/localization {currency:EUR, timezone:Asia/Shanghai} 200, P's fresh session then reads currency "EUR", timezone "Asia/Shanghai"; restored to currency null / timezone UTC afterwards
    access-security.me-permissions-aggregation-parity · r4 A6 pass HTTP-booted-server: W (wall-less org admin via update-member-role admin; permissionSets [showcase_member_default, organization_admin_no_bypass, member_default], no admin_full_access): objects.showcase_semantic_zoo present allowEdit:true and a zoo read 200; objects.showcase_project allowEdit:true; W creates a project 201 and PATCHes its name 200; objects.sys_secret absent and GET /data/sys_secret 403 PERMISSION_DENIED. Only 4 of 115 registered objects are missing from W's map, all private: sys_secret, sys_flow_credential, sys_connector_credential, sys_connector_connection
    access-security.record-access-explain · r4 A0 pass browser-live (/_console/studio/com.example.showcase/access, "Explain access", admin): user = A persona (showcase_auditor) / P persona, object showcase_private_note, record = probe 6PfNtH4HhTAzbzjh (owned by a third member M2). Screenshot shows the 10 layers (tenant_isolation, principal, required_permissions, object_crud, fls, owd_baseline, depth, sharing, vama_bypass, rls), each with its verdict badge. Record verdict for A: "Record … is VISIBLE … Decided by: View/Modify All bypass". For P: "… is NOT visible … Decided by: Sharing"
    access-security.record-access-explain · r4 A1 pass HTTP-booted-server: admin POST /api/v1/security/explain {object: showcase_private_note, operation: read, userId: A, recordId: probe} 200, record {visible:true, decidedBy:"vama_bypass"}, layer vama_bypass verdict widens, contributors [{permission_set showcase_auditor, via "direct grant"}]
    access-security.record-access-explain · r4 A2 pass HTTP-booted-server: A explain visible:true ∧ A's own GET /data/showcase_private_note/‹probe› 200. P explain visible:false ∧ P's own GET 404 RECORD_NOT_FOUND. The verdict agrees with the read for both personas
    access-security.record-access-explain · r4 A3 pass HTTP-booted-server: plain-member explain 200, record {visible:false, decidedBy:"sharing"}, 10 layers, no EXPLAIN_FAILED. negative[0]: the admin asked, principal.userId = P with sets [showcase_member_default, member_default], vama_bypass not_applicable, so it is not visible because the admin asked
    access-security.record-access-explain · r4 A4 pass browser-live + HTTP-booted-server: the panel's own POST /security/explain request (same body shape) answered a body byte-identical to the API-twin body for both personas. The DOM layer list (data-testid explain-layer-*) matches the body's layers in order and verdict for both (n/a ↔ not_applicable)
    access-security.share-link-landing-page · r6 A0 pass browser-live + network, fresh anonymous context, /_console/s/‹plain token› (link_only, per-link redact [summary]): resolve 200, record keys id…title,project,status,owner_id, with internal_notes, deal_value and summary absent and none of their values in the body; redactedFields [internal_notes, deal_value, summary]. Page (screenshot) renders "Shared showcase_client_brief" with the fields, no redacted value in the DOM, and the notice "Some fields are hidden by the owner: internal_notes, deal_value, summary"
    access-security.share-link-landing-page · r6 A1 pass browser-live + network: password link: 401 {code NEEDS_PASSWORD}, page "Password required"; wrong password via #share-pw + Continue → 401 {code WRONG_PASSWORD}, page re-prompts with "Wrong password."; correct → 200 and the record renders. Both 401 bodies carry no record and no secret value
    access-security.share-link-landing-page · r6 A2 pass browser-live (screenshots) + network: revoked → resolve 410, page "Can't open this link / This link has expired or was revoked."; short-expiry link after lapse → 410, the same designed message; garbage token → 404, "This link is invalid or no longer available.". The same anonymous resolves over HTTP give 410 {EXPIRED_OR_REVOKED} / 404 {INVALID_OR_EXPIRED} with no record. None of the pages is blank, a spinner or raw JSON
    access-security.share-link-landing-page · r6 A3 pass browser-live + network: signed_in link, anonymous fresh context: resolve 401 {code SIGN_IN_REQUIRED} with no record; page "Sign in required" + "The owner shared this link with signed-in users only."; the Sign in href is /_console/login?redirect=%2Fs%2F‹token›; 0 input[type=password]. Member P signed in through the console form (better-auth.session_token cookie + auth-session-token in localStorage): resolve 200 and the record renders
    access-security.share-link-landing-page · r6 A4 pass browser-live (screenshot): a fresh context with no session loads /_console/s/‹plain token› standalone. It has 0 nav/aside elements and no sidebar or console chrome, and the URL stays /_console/s/‹token› with no login redirect

    ai

    item · rev clause verdict layer · text evidence
    ai.mcp-coded-refusals-and-runtime-exposure · r4 A1 pass HTTP-booted-server: admin osk_ tools/call get_record {objectName:showcase_task, recordId:does-not-exist-g2} → isError:true, text {error:{code:"RECORD_NOT_FOUND",message:"Record does-not-exist-g2 not found in showcase_task",status:404}}; the id spelling → -32602 naming id → recordId
    ai.mcp-coded-refusals-and-runtime-exposure · r4 A2 pass HTTP-booted-server: PUT /meta/object qa_hidden_obj (enable.apiEnabled:false, sharingModel) and qa_shown_obj 200; query_records qa_hidden_obj → {error:{code:OBJECT_API_DISABLED,message:"object is not exposed via the API",status:404}}, qa_shown_obj → records [] total 0; boot stopped (group SIGTERM) and re-booted on the same file DB: the first call after the boot got the same OBJECT_API_DISABLED 404, and qa_shown_obj was still served; after DELETE /meta/object (200 both): list_objects no longer lists either, query → OBJECT_NOT_FOUND 404, describe → 'Object "qa_hidden_obj" not found'
    ai.mcp-coded-refusals-and-runtime-exposure · r4 A3 pass HTTP-booted-server: staged qa_ai_mark (PUT /meta/action 200, declarative update progress:50, ai.exposed); auditor.demo osk_ run_action on task 1rBi5zPB9S_3Ik2h → isError, body keys ["error"] → ["code","message","status"] = PERMISSION_DENIED / generic message / 403; scanning the raw body found no position, permissionSet, details, grant or profile key; the task's progress stayed 100. Admin control on task 5HO3aK-Qql7q-HBd → ok:true, progress 45→50
    ai.mcp-http-surface · r8 A0 pass HTTP-booted-server (OS_MCP_SERVER_ENABLED=false boot :41622): GET /mcp/skill as anon and as a session caller → 404 {code:RESOURCE_NOT_FOUND,message:"MCP server is not enabled"}; POST /mcp as a session bearer and as an osk_ caller → the same 404; anonymous POST /mcp → 401 UNAUTHENTICATED "Unauthorized: a valid API key is required", with no WWW-Authenticate header. Observed the 404 (switch off), never the 501. N1 held
    ai.mcp-http-surface · r8 A1 pass HTTP-booted-server (default boot :41621): anon GET /api/v1/mcp/skill → 200, content-type "text/markdown; charset=utf-8", content-disposition 'inline; filename="SKILL.md"', cache-control no-store; the body starts with front-matter name: objectstack, and its "## Connect" block carries http://localhost:41621/api/v1/mcp
    ai.mcp-http-surface · r8 A3 pass HTTP-booted-server: OAuth-live loopback boot: anon POST /mcp initialize → 401 UNAUTHENTICATED "Unauthorized: a valid OAuth access token or API key is required" + WWW-Authenticate 'Bearer realm="ObjectStack MCP", resource_metadata="http://localhost:41621/.well-known/oauth-protected-resource"'; the OAuth-dark boots (MCP-off :41622, public 192.0.2.2 :41624) → "Unauthorized: a valid API key is required", no header. N0: anon tools/call query_records and tools/list → 401 before any tool ran
    ai.mcp-http-surface · r8 A4 pass HTTP-booted-server: admin osk_ (minted via POST /api/v1/keys 201): initialize 200 protocolVersion 2025-03-26, capabilities {prompts,tools}; tools/list = 12 names, exactly the documented set (list_objects, describe_object, validate_expression, query_records, aggregate_records, get_record, create_record, update_record, delete_record, list_actions, run_action, resume_run): none missing, none extra, no degradation
    ai.mcp-http-surface · r8 A5 pass HTTP-booted-server: real embedded-AS token (native DCR + auth-code + PKCE + consent), scope "openid profile" → initialize 403 PERMISSION_DENIED "Forbidden: the access token grants none of the MCP scopes (data:read, data:write, actions:execute, metadata:write)" + WWW-Authenticate 'Bearer error="insufficient_scope", scope="data:read data:write actions:execute metadata:write", resource_metadata=…'; a data:read token → tools/list = list_objects, describe_object, validate_expression, query_records, aggregate_records, get_record only, and create_record → "Tool create_record not found"
    ai.mcp-oauth-private-host-transport · r6 A0 pass HTTP-booted-server: boot (a) OS_AUTH_URL=http://10.0.0.5:41623. The address was NOT held: the item's fixtures.requires[0] (r6) says an unheld literal stages this clause in full. Both PRM paths → 200 {resource:"http://10.0.0.5:41623/api/v1/mcp", authorization_servers:["http://10.0.0.5:41623/api/v1/auth"], 4 MCP scopes+offline_access}; anon /mcp 401 resource_metadata="http://10.0.0.5:41623/.well-known/oauth-protected-resource". The advertised origin is the private one, never localhost (negative[1])
    ai.mcp-oauth-private-host-transport · r6 A1 pass HTTP-booted-server: boot (b) OS_AUTH_URL=http://192.0.2.2:41624 (TEST-NET-1, PUBLIC to the rule; held by this container, so every request went to 192.0.2.2 itself): both PRM paths → 404 ENDPOINT_NOT_FOUND; anon /mcp 401 with no WWW-Authenticate; the boot log has 1× "OAuth discovery is served over PUBLIC plain HTTP" and 1× "MCP server is enabled but the OAuth track is NOT live (base URL fails the OAuth 2.1 transport rule …)", and 0× "OAuth is served UNENCRYPTED". N0 and N2 held
    ai.mcp-oauth-private-host-transport · r6 A2 pass log (captured boot logs): "OAuth is served UNENCRYPTED": (a) 1 at WARN; (c) 1 at INFO in the --log-level info boot and 0 in the default warn-level re-boot on the same port; (b) 0; https 0. "OAuth discovery is served over PUBLIC plain HTTP": (b) 1 (the line states the AS discovery surface, MCP OAuth track DISABLED, TLS the remedy); (a) 0, (c) 0, https 0. Issuers named: http://10.0.0.5:41623/api/v1/auth, http://localhost:41621/api/v1/auth, http://192.0.2.2:41624/api/v1/auth; 「OAuth 未加密」 0 everywhere
    ai.mcp-oauth-private-host-transport · r6 A3 pass HTTP-booted-server: boot (c) loopback :41621, both PRM paths → 200 {resource:"http://localhost:41621/api/v1/mcp", authorization_servers:["http://localhost:41621/api/v1/auth"], same scopes_supported/bearer_methods/resource_name}, the same shape as (a) with the loopback origin in place of the private one; anon 401 carries the loopback resource_metadata pointer
    ai.mcp-oauth-private-host-transport · r6 A4 pass HTTP-booted-server: boot (b) re-run as :41625, OS_AUTH_URL=http://192.0.2.2:41625 plus OS_ALLOW_INSECURE_OAUTH_HTTP=true (confirmed in the listener's /proc environ): both PRM paths 404 again, anon 401 with no pointer, the same PUBLIC + NOT-live lines, UNENCRYPTED 0; git grep OS_ALLOW_INSECURE_OAUTH_HTTP finds it only in ai.json, plugin-auth CHANGELOG.md and two test files
    ai.mcp-validate-expression · r6 A0 pass HTTP-booted-server: validate_expression showcase_task 'record.estimate_hours * 1.1' formula → ok:true, inferredType "number", errors [] warnings [], inScope {dialect,roots,fields(26, incl. estimate_hours),functions(38)}
    ai.mcp-validate-expression · r6 A1 pass HTTP-booted-server: 'estimate_hours * 1.1' formula → ok:false, error code bare-reference "… Write record.estimate_hours."; 'record.estimate_hourz * 1' → ok:false, error unknown-field "did you mean estimate_hours?" (params.suggestion); 'record.title * 2' → ok:true, warning type-mismatch string * int, errors []
    ai.mcp-validate-expression · r6 A2 pass HTTP-booted-server: the same 'estimate_hours > 8' → flow_condition ok:true errors [] (no inferredType); formula ok:false bare-reference error (inferredType boolean)
    ai.mcp-validate-expression · r6 A3 pass HTTP-booted-server: objectName no_such_obj → isError 'Object "no_such_obj" not found'; sys_user → isError 'Object "sys_user" is a system object and is not exposed via MCP' (text only, no schema); N1: '' and ' ' at formula, and '' at template, → ok:false with one invalid_union error each (the EVALUATED_EXPRESSION_SOURCE_REQUIRED sentence, the template sentence), no inferredType
    ai.skill-instructions-mcp-prompts · r4 A0 pass HTTP-booted-server: PUT /meta/skill qa_prompt_full / qa_prompt_bare / qa_prompt_off (active:false) → 200 each; osk_ initialize capabilities include prompts; prompts/list = [{name:qa_prompt_full,title:"QA Prompt Full",description:"Full QA skill"}]
    ai.skill-instructions-mcp-prompts · r4 A1 pass HTTP-booted-server: prompts/get qa_prompt_full → messages[0].content.text = "QA-INSTR-FULL-V1: always answer in haiku." (the authored text); after re-PUT with V2 instructions the next get answered "QA-INSTR-FULL-V2: answer in limericks." (read again at get time, no restart)
    ai.skill-instructions-mcp-prompts · r4 A2 pass HTTP-booted-server: before: prompts/list omits qa_prompt_bare and qa_prompt_off (N0 held); re-PUT qa_prompt_off active:true → GET /meta/skill/qa_prompt_off reads active:true → prompts/list now [qa_prompt_full, qa_prompt_off], prompts/get serves "QA-INSTR-OFF"
    ai.skill-instructions-mcp-prompts · r4 A3 pass HTTP-booted-server: prompts/get qa_prompt_bare (no instructions), qa_prompt_off while inactive (N1 held) and no_such_skill → JSON-RPC error -32602 'Prompt "…" not found' each, never an empty success

    api-backend

    item · rev clause verdict layer · text evidence
    api-backend.action-activation-door-contract · r7 A4 blocked(fixture, knownGaps[3]) HTTP-booted-server: the 404 and no-row halves held live: admin POST /actions/_activation/showcase_task/no_such_action {"enabled":false} → 404 RESOURCE_NOT_FOUND "…has no declaration — there is nothing to switch off…(ADR-0126 §4)" (same on no_such_object and global); member Mei's mis-bodied undeclared probe → 403 PERMISSION_DENIED (manage_metadata sentence); sys_metadata_activation 0 rows before and after every refusal (node:sqlite count 0; GET /data/sys_metadata_activation total 0). The metadata-plane 503 arm (declaration.degraded, only when resolveRouteActionDeclaration throws, action-execution.ts:4490) has no stock door and no pin
    api-backend.bulk-write-contract · r6 A0 pass HTTP-booted-server: member m1 POST /data/showcase_private_note/createMany bare array of 3 → 201 {object, records[3]}, each owner_id/created_by/updated_by = m1, organization_id = Default Org, pinned default false; {records:[…]} wrapper → 400 VALIDATION_FAILED fields[0] {records, invalid_type}. Single-door POST row: GET key sets identical (12 keys) and stored non-null column sets identical (body,created_at,created_by,id,organization_id,owner_id,pinned,title,updated_at,updated_by)
    api-backend.bulk-write-contract · r6 A1 pass HTTP-booted-server: m1 updateMany {records:[{id,data:{body}}×3 markers]} → 200 total 3 succeeded 3; admin re-read: 3/3 markers body 'upd-r1', control single note body 'control' and m2's note body 'foreign-orig' unchanged; {where,data} → 400 VALIDATION_FAILED fields[0] {records, required}
    api-backend.bulk-write-contract · r6 A2 pass HTTP-booted-server: m1 updateMany nonexistent id → 200 success:false succeeded 0, row RECORD_NOT_FOUND; m2's foreign note id → 200 success:false, row RECORD_NOT_FOUND (same code, same message shape); both in one call under continueOnError:true → both RECORD_NOT_FOUND; foreign row body/updated_at identical before/after (node:sqlite); records:[] → 200 total 0; per-object /batch update own+foreign (continueOnError) → own success, foreign RECORD_NOT_FOUND; admin repeat deleteMany of deleted ids → row0 RECORD_NOT_FOUND, rest NOT_ATTEMPTED. N0 held (foreign row neither counted nor mutated)
    api-backend.bulk-write-contract · r6 A3 pass HTTP-booted-server: admin createMany 201 records → 400 BATCH_TOO_LARGE {count:201,max:200}; per-object /batch create 201 → 400 BATCH_TOO_LARGE; that batch's marker rows in DB = 0; control createMany 200 → 201
    api-backend.bulk-write-contract · r6 A4 pass HTTP-booted-server: m1 deleteMany 2 own ids → 200 row0 PERMISSION_DENIED, row1 NOT_ATTEMPTED, 3/3 markers still present; m1 deleteMany foreign id → PERMISSION_DENIED (object gate first, as steps[5] says); admin deleteMany 3 marker ids → 200 succeeded 3; GET each → 404; single control note and foreign note → 200 (remain); {where} body → 400 fields[0] {ids, required}
    api-backend.bulk-write-contract · r6 A5 pass HTTP-booted-server: grep ERR_BULK_RESULT_MISMATCH over every captured response and boot log → 0 hits (positive control: BATCH_TOO_LARGE 2 hits in the same capture)

    approvals

    item · rev clause verdict layer · text evidence
    approvals.act-door-and-admin-rescue-slate · r5 A1 pass HTTP-booted-server (os dev self-hosted mount) + pins: PUT /api/v1/approvals/act?token=garbage → 404 application/json {error.code ENDPOINT_NOT_FOUND}, no Allow header (byte-identical to the unmounted control PUT /approvals/no_such_door_xyz); GET and form POST token=garbage → 200 text/html "Invalid link · 链接无效", no summary rows; DELETE/PATCH also 404 ENDPOINT_NOT_FOUND; requests unchanged before/after. Live-token GET-renders/POST-redeems halves pin-scored per verify: hono-approvals-act.conformance 4 + action-page-member 12 passed at this subject (#28929 A0)
    approvals.email-action-token-door · r10 A0 blocked(fixture, knownGaps[0]) HTTP-booted-server (live at-rest leg held) + pins: admin POST /approvals/requests/‹invoice req›/remind → 200, actions gained one remind row (actor Dev Admin); the approval.reminder sys_notification row's payload = {title, body, actionUrl:"/system/approvals?request=…"} — no actions key, no /approvals/act URL, no token=; sys_approval_token 0 rows (columns token_hash…, no raw column); GET /data/sys_approval_token → 404 OBJECT_API_DISABLED (designed). Pins passed (#28929). The non-pending-approver FORBIDDEN arm has no pin and no door: issueActionTokens has no production caller at the subject (re-grepped)
    approvals.email-action-token-door · r10 A1 blocked(fixture, knownGaps[0]) none live: the byte-identity of the actions timeline and the token's consumed_at across repeated GETs needs a minted raw token; no stock door mints one (issueActionTokens' only references are its definition; /approvals/act producers are routing only). Pins for the status/decision half passed (#28929)
    approvals.email-action-token-door · r10 A3 blocked(fixture, knownGaps[0]) none live: the consume-FIRST half (a decide that fails still burns the token) needs a raw token; replay halves pin-held (#28929)
    approvals.email-action-token-door · r10 A4 blocked(fixture, knownGaps[0]) HTTP-booted-server (invalid shape held): clean anonymous client (GET /approvals/requests → 401) GET and form POST /approvals/act?token=garbage → 200 text/html "Invalid link · 链接无效", body has only the badge, "This link is not recognized" and the inbox link — no summary rows; 3 pending requests + their actions byte-identical before/after. The not_pending / not_approver PAGES with summary rows need a live token (no door, no page-level pin)
    approvals.email-action-token-door · r10 A5 blocked(fixture, knownGaps[0]) none: no raw token exists to present as Bearer; converse pin (token is the only credential) passed (#28929)
    approvals.request-page-reply-and-reader-visibility · r5 A1 pass HTTP-booted-server: GET /meta/page/sys_approval_request_detail → 200 with 1 'record:approval_decision'; on seeded expense request areq_97a8…: {body} → 400 VALIDATION_FAILED 'comment is required'; Ada Auditor (pending approver) and Mei Phone (submitter) POST …/comment {comment} → 200 each; GET …/actions comment rows 0 → 2 (actor auditor, actor phone); member m2 (neither) → 403 FORBIDDEN "actor … is not on this request", no row
    approvals.request-page-reply-and-reader-visibility · r5 A2 pass HTTP-booted-server on a run-authored grant (scratch package com.qa.g3reader: permission set qarv_req_reader {sys_approval_request: allowRead} via position qarv_reqreader bound to member m3): m3 GET /data/sys_approval_request/‹expense req› → 404 RECORD_NOT_FOUND, body identical (modulo id) to a nonexistent id's 404; m3 list → 200 records [] total 0 ($count 0) vs admin total 12. No-grant leg: m2 and Mei (submitter-participant) by-id and list → 403 PERMISSION_DENIED
    approvals.request-page-reply-and-reader-visibility · r5 A3 pass HTTP-booted-server on a run-authored object (qarv_doc in scratch package com.qa.g3reader, sharingModel public_read, enable.approvalsVisibleToReaders) + scratch flow w4r_reader; reader m2 (read via position qarv_reader): flag ON → GET /approvals/requests?object=qarv_doc&recordId=DOC lists the request, by-id 200 viewer {can_act:false,is_submitter:false,can_override:false}, actions [submit]; inbox (no record) [] and my-pending []; approve → 403 FORBIDDEN, comment → 403; m1 (no record read) list [] by-id 404. PUT object flag OFF (no restart) → m2 record list [], by-id 404, actions 404; flag ON again → visible again
    approvals.request-page-reply-and-reader-visibility · r5 A4 pass browser-live (Playwright chromium, form sign-in, console from packages/console/dist): /_console/apps/setup/sys_approval_request/record/areq_97a8… as Ada Auditor (pending approver): decision panel renders — "Sign-off progress — 0 of 2 groups" (finance 0/1, manager 0/1), "Waiting on" Dev Admin · manager / Ada Auditor (demo) · finance, buttons Approve / Reject / Reassign / Send back / Request info / Reply, Timeline 3
    approvals.status-mirror-field · r6 A0 pass HTTP-booted-server: scratch flow w4m_r1 (POST /automation; approval node approvers [{type:user, admin}], approvalStatusField f_textarea, lockRecord true, maxRevisions 1) on showcase_field_zoo, submitter m1 (contributor-bound): pending (updated_by m1) → approve 'approved' (updated_by admin); reject 'rejected'; submitter recall 'recalled' (updated_by m1); revise 'returned' (admin); resubmit → 'pending' (m1); second revise past maxRevisions → request rejected, field 'rejected'
    approvals.status-mirror-field · r6 A1 pass HTTP-booted-server: record X_GC5-QPm7noYtjG pending with f_textarea='pending' already landed; submitter m1 PATCH {f_email} → 409 RECORD_LOCKED "…locked while an approval is in progress…"; f_email stayed null, updated_at unchanged
    approvals.status-mirror-field · r6 A2 pass HTTP-booted-server (wall-clock offset shim per approvals.sla-escalation fixtures) + pin: boot 41632 with NODE_OPTIONS --require shim; flow w4s_sla (escalation auto_approve, timeoutHours 1, mirror f_textarea) + record_change cascade w4c_cascade (runAs default 'user', stamps f_email). Human approve → mirror 'approved', cascade run completed user_id = decider, f_email 'cascaded@example.com'. Offset +2h02m, restart → boot sweep: request approved, actions [submit, escalate actor null 'auto_approve', approve actor null 'SLA escalation'], mirror 'approved', cascade run FAILED user_id null "[runAs] refusing a data operation … no trigger user could be resolved", f_email null. Dead-run half: status-mirror-cascade 2 passed (#28929)
    approvals.status-mirror-field · r6 A3 pass HTTP-booted-server: flow w4n_r1 (no approvalStatusField): record created f_textarea='seed-value' stayed 'seed-value' and updated_by m1/updated_at unchanged through submit+approve; second record f_textarea null stayed null through submit+reject
    approvals.status-mirror-field · r6 A4 pass HTTP-booted-server + log: flow w4b_r1 (approvalStatusField f_does_not_exist): approve → 200 {finalized:true, decision approve, resumed:true}; request approved; actions [submit m1, approve admin]; run completed; boot log "WARN [approvals] mirrorStatusField failed: Unknown field 'f_does_not_exist' on object 'showcase_field_zoo'" (twice: open and decide)

    Generated by Claude Code

  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbZx1yZXiTrFXKbSdFZ

    Per-clause verdicts (2/2): attachments-storage, automation, cli. Live legs only; the #28929 pass rows keep their verdict and are not repeated.

    attachments-storage

    item · rev clause verdict layer · text evidence
    attachments-storage.attach-requires-parent-edit · r9 A2 pass HTTP-booted-server: C-uploaded attachment on qa_shared: A (reads, no edit) DELETE 403 ATTACHMENT_DELETE_DENIED, B (no read) DELETE 404 RECORD_NOT_FOUND, row survives both (admin GET 200, DB count 1); uploader C with parent edit removed (row re-owned to admin; C PATCH row 403, C read 200) DELETE own attachment 200, row gone. Persona C rebound qa_att_editor→qa_att_reader first (see Accuracy: editor set has no sys_attachment delete, so RBAC 403 PERMISSION_DENIED answered ahead of the hook)
    attachments-storage.attach-requires-parent-edit · r9 A5 pass HTTP-booted-server: admin PATCH sys_attachment {parent_object:qa_nofiles,parent_id:N} 403 FILES_DISABLED; re-read (API + DB) parent_object qa_shared / parent_id unchanged; half-applies refused too (parent_object only 403 FILES_DISABLED, parent_id only 403 ATTACHMENT_PARENT_ACCESS, row unchanged); control re-point onto files-enabled qa_vault 200 (N2 held)
    attachments-storage.download-authz-both-sides · r10 A2 pass HTTP-booted-server: admin file on admin-owned qa_shared: A and C /url 200 {success:true,data:{url}}, url followed 200 byte-equal; B 403 ATTACHMENT_DOWNLOAD_DENIED. Uploader bypass: C's own file attached to C-created qa_vault row then re-owned to admin (C GET row 404): C /url 200, bytes equal, /files 302; A/B 403 ATTACHMENT_DOWNLOAD_DENIED; gated grant TTL 300 s vs presign 3600 s (exp read from the minted token)
    attachments-storage.download-authz-both-sides · r10 A4 pass browser-live (built /_console, 1600px, three-column, panel resident): persona A form sign-in (get-session = qa-vault-a), qa_shared record panel listed 4 files; admin DELETE A's qa_att_reader binding 200 without reload; Download click → GET /api/v1/storage/files/ID/url 403 ATTACHMENT_DOWNLOAD_DENIED; panel role=alert "You don't have access to download this attachment." (no "Download failed", no JSON; N1 held); A re-bound 201
    attachments-storage.inline-grid-receipt-cells · r5 A2 pass browser-live (built /_console, twice on fresh loads): picking receipt.png fired POST /api/v1/storage/upload/presigned 200, PUT /api/v1/storage/_local/raw/TOKEN 200, POST /api/v1/storage/upload/complete 200 (then GET /files/ID 302 → raw 200 for the thumbnail); chip file-cell-chip "receipt.png" with a remove button before submit
    attachments-storage.inline-grid-receipt-cells · r5 A3 pass browser-live + file-DB read (twice): POST /api/v1/batch 200, body {results:[invoice,line]} no errors; line op receipt = bare uuid string (not data:/blob:/object, not the name; N1 held); sys_file row status committed, ref_object showcase_invoice_line, ref_id = the created line id, ref_field receipt, acl private
    attachments-storage.orphan-tombstone-reap · r4 A1 pass HTTP-booted-server scheduler sweep (restart, initial sweep at +60 s): tombstone T1 (deleted_at -31d) row gone AND bytes absent; pending P1 (created_at -8d) row + bytes gone. Failed byte delete staged by replacing T2's byte file with a directory: served log "WARN [storage] reap guard: reclaim failed for sys_file ID (EISDIR: illegal operation on a directory, unlink …); retrying next sweep", T2 row kept status deleted. Blocker removed + restart: next sweep reaped T2 row and bytes (retry proven). Fresh tombstone T3 and committed control unchanged
    attachments-storage.presigned-upload-roundtrip · r3 A0 pass HTTP-booted-server: presign 200 {success,data:{uploadUrl,method:PUT,headers,fileId,expiresIn:3600,downloadUrl}}; GET /data/sys_file/ID status pending, owner_id = admin id despite body owner_id/ownerId SPOOFED_OWNER (DB agrees, scope attachments, acl private)
    attachments-storage.presigned-upload-roundtrip · r3 A1 pass HTTP-booted-server: sys_file status pending before POST /upload/complete (200), committed after (API and DB reads)
    attachments-storage.presigned-upload-roundtrip · r3 A2 pass HTTP-booted-server: /files/ID/url 200 {success:true,data:{url}} (keys exactly success,data / url); GET url 200, 35 bytes, sha256 equal to upload; presign downloadUrl followed 200 (N1 held)
    attachments-storage.presigned-upload-roundtrip · r3 A3 pass network (HTTP-booted-server): GET /files/ID 302, Location identical to the /url signed url; following it 200 byte-equal
    attachments-storage.presigned-upload-roundtrip · r3 A4 pass HTTP-booted-server: anonymous presign 401 {success:false,error:{code:AUTH_REQUIRED}}; sys_file count 1→1; no "[storage] no session resolver" line in the boot log (N0 held)
    attachments-storage.read-inherits-parent-rls · r9 A3 pass HTTP-booted-server + served log: ordinary half: 2309 sys_attachment rows (visible rows at both ends, 2300 vault filler between) — A and C served 6/6 incl. first+last, total = rows served, vault rows 0; admin 2309/2309; no '-row cap' / 'attachment read visibility' line in the log. Probe half reached live via a runtime federated parent (qa_ext on showcase_external, enable.files) with 2105 rows: log "WARN [storage] attachment read visibility: the bounded probe's candidate pre-scan (federated-parent: qa_ext, …) hit the 2000-row cap; the visibility filter for these rows is fail-closed and may omit visible rows"; A served 2100 (2 visible rows outside the window omitted, 3 dangling excluded, vault 0) — exclusion, no leak; B (no qa_ext read) 0 qa_ext rows
    attachments-storage.sys-file-status-pipeline · r5 A0 pass HTTP-booted-server reads (GET /data/sys_file): pending after presign → committed after complete → committed after DP join delete → deleted (deleted_at set) after last join delete → committed (deleted_at null) after re-attach
    attachments-storage.sys-file-status-pipeline · r5 A1 pass HTTP-booted-server: pending file /url 404 FILE_NOT_FOUND and /files 404 FILE_NOT_FOUND (no Location); also after the PUT, before complete: /url 404 FILE_NOT_FOUND
    attachments-storage.sys-file-status-pipeline · r5 A2 pass HTTP-booted-server: holder-less tombstone /url and /files 404 FILE_NOT_FOUND; deleted row + live join row inserted behind the hooks (driver write): /url 200 bytes equal, /files 302, sys_file status/deleted_at/updated_at byte-identical before and after (tombstone not rewritten); holder removed → 404 again; deleted + ref_* owner stamp (driver) → /url 200; hooked re-attach → row committed/deleted_at null BEFORE any download, then /url 200 bytes equal
    attachments-storage.sys-file-status-pipeline · r5 A5 pass HTTP-booted-server: user-scope file joined to Website Relaunch then join deleted → committed, deleted_at null before and after; user-scope field-owned file (qa_shared.doc, ref_object stamped) joined then join deleted → unchanged
    attachments-storage.upload-limits-scope-and-ownership · r4 A1 pass HTTP-booted-server: 101 MiB presign 413 PAYLOAD_TOO_LARGE naming 100 MB, sys_file 7→7 (99 MiB control 200); PUT /api/settings/storage max_upload_mb=1 200; 2 MiB presign 413 naming 1 MB without restart, count 8→8; chunked init 2 MiB 413; 0.5 MiB 200; restored to 100
    attachments-storage.upload-limits-scope-and-ownership · r4 A2 pass HTTP-booted-server: scope avatars / PUBLIC / public / null → 400 INVALID_REQUEST naming user, tenant, private, temp, attachments ('public' gets the "never made a file publicly readable" sentence); omitted → 200, sys_file scope user
    attachments-storage.upload-limits-scope-and-ownership · r4 A5 pass HTTP-booted-server + driver write: qa_shared gained file fields doc/doc2; raw UPDATE planted R1's file id into R2.doc2 (raw column read back holds it); reader D (qa_shared read only; GET sys_file 403) GET R2: doc {id,name,size,mimeType,url}, doc2 {id, metadataRefused:true}; admin (sys_file read) gets full metadata for doc2 as the step predicts
    attachments-storage.upload-session-abort · r8 A0 pass HTTP-booted-server + file-DB reads: init 200 {uploadId,resumeToken,fileId,totalChunks 2,chunkSize 5242880,expiresAt}; row in_progress 0 → PUT 0 (eTag) row 1, progress {uploadedChunks 1, percentComplete 83, status in_progress} → PUT 1 row 2 → complete 200, row completed 2/2, sys_file committed; progress then status completed
    attachments-storage.upload-session-abort · r8 A3 pass HTTP-booted-server scheduler sweep + served log: abandoned session (expires_at -2d) with its parts dir made immutable (chattr +i): log "WARN [storage] reap guard: multipart abort failed for sys_upload_session ID (ENOTDIR …); retrying next sweep", row kept in_progress with backend_upload_id; sibling abandoned session reaped and parts gone; attribute removed + restart → next sweep reaped the row and its parts (N0 held)
    attachments-storage.upload-session-abort · r8 A4 pass HTTP-booted-server: settled statuses read in_progress, completed, failed, expired. 'failed' driven live (part file removed from the backend before complete → 500 INTERNAL, row failed; re-PUT + re-complete → completed); bogus eTag 409 RESOURCE_CONFLICT, row stays in_progress. Retention: completed/failed/expired rows with created_at -8d and expires_at not past TTL all reaped; in_progress control with created_at -8d kept (onlyWhen holds). 'completing' transient per knownGaps[1]
    attachments-storage.upload-session-abort · r8 A5 pass HTTP-booted-server: expires_at backdated 60 s (driver, no sweep): chunk PUT 410 UPLOAD_SESSION_EXPIRED, row expired, uploaded_chunks 1 unchanged; complete 410 UPLOAD_SESSION_EXPIRED; progress 200 status expired; in-deadline session chunk PUT 200

    automation

    item · rev clause verdict layer · text evidence
    automation.trigger-type-matrix · r10 A0 pass HTTP-booted-server (+browser for screen): one run per variant, trigger = launch event: record_change run_fc104edc showcase_urgent_task_alert (Flow.type record_change) {type:record-after-write, object:showcase_task, recordId:kyy-DTdhm-p0szAD}; autolaunched run_e12f4c42 showcase_batch_reminders (autolaunched) {type:manual}; schedule run_153ee789 showcase_scheduled_digest (schedule) {type:schedule}; screen run_706616fa showcase_reassign_wizard (screen) {type:manual, object:showcase_task, recordId:1uVksVze6EflHY2B}; api run_fe097e72 showcase_inbound_task_webhook (api) {type:api}; time_relative run_2f8d2a56 qa_g5_due_fast (fast clone per fixtures.requires[1], Flow.type schedule hosting config.timeRelative, interval 60000) {type:time_relative, object:showcase_task, recordId:cCey-DFQU_dY7yd3}. N0: anonymous POST /automation/showcase_batch_reminders/trigger and /showcase_reassign_wizard/trigger -> 401 UNAUTHENTICATED, run count 1->1 for each
    automation.trigger-type-matrix · r10 A1 pass HTTP-booted-server: urgent_task_alert runs before 0, after the mutations exactly 2: recordId kyy-DTdhm-p0szAD (created urgent) and 1uVksVze6EflHY2B (medium -> PATCH urgent); the later title-only save of 1uVk produced no run. The in_review -> done task AzKBJhwPUSCAtOCe fired showcase_task_completed and showcase_task_done_notify_owner once each, and its later title-only save fired nothing
    automation.trigger-type-matrix · r10 A2 pass HTTP-booted-server: POST /automation/hooks/showcase_inbound_task_webhook/intake with t=‹now›,v1=HMAC('‹t›.‹body›') -> 202 {accepted:true,messageId:msg_faee6c49…}; task PH1J2di41Wv7v--- title/assignee/project = posted body (g5 hook …, g5.assignee@example.com, GOHSJxcxq3rVq9Fk), status todo. N1: body-only sha256=, wrong key and no header -> 401 INVALID_SIGNATURE, task total 14->14
    automation.trigger-type-matrix · r10 A3 pass HTTP-booted-server (flag boot): showcase_scheduled_digest runs completed at 07:36:00.844, 07:37:00.843, 07:38:00.849, 07:39:00.852, 07:40:00.847Z, all trigger.type schedule, no manual help; sys_inbox_message project.digest rows for admin at 07:36:00.883, 07:37:00.879, 07:38:00.952, 07:39:00.923, 07:40:00.911Z
    automation.trigger-type-matrix · r10 A4 pass browser-live + HTTP-booted-server: row action click captured POST /api/v1/actions/showcase_task/showcase_bulk_reassign {recordId:1uVk…} -> 200 {status:paused, runId:run_706616fa…, screen.nodeId:collect}; GET run -> paused; Submit captured POST /automation/showcase_reassign_wizard/runs/run_706616fa…/resume {inputs:{new_assignee}} -> 200; GET run -> completed, steps start/collect/apply success; dialog closed, new assignee shown in grid; no /trigger POST
    automation.durable-suspend-restart · r4 A0 pass HTTP-booted-server: GET /data/sys_automation_run/:id: run_23600cd6 showcase_task_follow_up node hold (wait) paused vars 2224B ctx 2066B; run_d99d3142 showcase_project_closure node signoff (subflow) paused correlation subflow:run_5235b5b1 vars 2668B; run_5235b5b1 showcase_closure_signoff node ask_signoff (approval) paused correlation areq_ae4f2657 vars 2751B
    automation.durable-suspend-restart · r4 A1 pass HTTP-booted-server (two spawned processes): boot A's process group SIGTERMed 07:41:47Z (port free, rows paused on disk); cold boot B over the same file DB ready 07:42:13Z; run_d99d3142 and run_5235b5b1 re-read paused with identical node, correlation, payload sizes and updated_at; run_23600cd6 still paused at the re-read; seeded data present (5 projects, task VEHdkRYC4kl2Xlq6)
    automation.durable-suspend-restart · r4 A2 pass HTTP-booted-server (boot B): run_23600cd6 completed 07:42:41.164Z (remind step finished 54 s after boot A stopped, with no manual resume); sys_inbox_message task.followup "Follow up on: g5 durable follow-up" for admin at 07:42:41.193Z
    automation.durable-suspend-restart · r4 A3 pass HTTP-booted-server (boot B): before: parent paused at signoff, child paused at ask_signoff; POST /approvals/requests/areq_ae4f2657…/approve -> 200 status approved; child completed output {decision:approved} (mark_approved ran); parent completed (signoff, notify_owner success); inbox row for usr_showcase_auditor_demo body "Closure sign-off decision for "Website Relaunch": approved."
    automation.durable-suspend-restart · r4 A4 pass HTTP-booted-server: after resume GET /data/sys_automation_run/‹id› -> 404 RECORD_NOT_FOUND for run_23600cd6, run_d99d3142 and run_5235b5b1; each run's history reads completed. N0 held: no row vanished across the restart, and no completed run left a paused row
    automation.paused-run-trigger-record-masked · r5 A0 pass HTTP-booted-server, admin AND member (g5.member, a member holding only the runtime set g5_run_reader with read on sys_automation_run + g5_echo): GET /data/sys_automation_run/run_12363338 -> 200 paused; variables_json record/$record/previous and context_json record/previous: g5_password and g5_token = SECRET_MASK '••••••••', the unset password field = null, internal g5_hidden key absent, name = written value; leak scan clean (plaintext pw old/new, token, internal value, 'secret:'). Arming: the primary DB row holds the plaintext password, a secret:sec_… handle and the internal value
    automation.paused-run-trigger-record-masked · r5 A1 pass HTTP-booted-server, admin and member: GET /automation/qa_g5_mask_pause/runs/run_12363338 -> 200; variables.record, .previous and .$record: password and token masked, unset field null, g5_hidden absent, name kept; leak scan clean. negative[0] held for both personas, hot and after the cold boot (run_49e47d1a read by both on boot B)
    automation.paused-run-trigger-record-masked · r5 A3 pass HTTP-booted-server (two spawned processes): run_49e47d1a paused on boot A; after SIGTERM and cold boot B over the same file, resumed on B -> 200; echo row "g5 echo c1": seen_name=g5 vault c1 renamed, seen_password/seen_token/seen_previous_password = mask, seen_hidden = ABSENT; paused row then 404 RECORD_NOT_FOUND
    automation.flow-registration-verdict-on-read · r2 A1 pass HTTP-booted-server: GET /meta/flow/showcase_urgent_task_alert -> registration {state:registered}; scratch qa_g5_refused_flow (type api, no secret; saved 200 with OS_ALLOW_UNLINTED_METADATA_WRITES=1) -> registration {state:refused, reason.message "…its start node declares no config.secret…"} while item.status reads active; same after the cold boot
    automation.flow-registration-verdict-on-read · r2 A2 pass HTTP-booted-server: GET /meta/flow -> {type, items[35], registrations{35}}; keys equal the item names; qa_g5_refused_flow refused, showcase_urgent_task_alert registered; states registered 34, refused 1
    automation.flow-run-semantics-v18 · r3 A1 pass HTTP-booted-server: scratch flow qa_g5_stamp_user (record-after-update on showcase_private_note, writes current_user.id into body); member g5.member PATCH own note 36AVsIh_zeghkv7M title -> run_159e5070 completed, trigger.userId O1d2r4YA… (member); read-back body = O1d2r4YA3PmXvjeOal7kaGT3YgqY1mh6 = member id (not the admin id)
    automation.inbound-hook-timestamped-signature · r2 A1 pass HTTP-booted-server: valid t=‹now›,v1= -> 202 {"accepted":true,"messageId":"msg_e5e0346d-…"}; run count 1 -> 2, task gB15o1qaX_opss-w created
    automation.inbound-hook-timestamped-signature · r2 A2 pass HTTP-booted-server: body-only sha256=, t-400 s, unknown flow, wrong hook id (plus wrong key, no header) -> 401, all six bodies byte-identical {"success":false,"error":{"code":"INVALID_SIGNATURE","message":"Signature verification failed."}}; run count 1 -> 1, task total 14 -> 14
    automation.scheduled-work-opt-in · r2 A1 pass HTTP-booted-server (default boot, flag unset): banner "1 flow declares a 'schedule' trigger but is NOT bound — disabled by deployment policy — … (OS_AUTOMATION_SCHEDULED_WORK_ENABLED is unset or not truthy) …: showcase_scheduled_digest" (and the same for time_relative: showcase_task_due_reminder); _status bound:false unboundCause deployment_policy for both; digest and due_reminder runs 0 at 07:43:07Z, 8 min after ready
    automation.scheduled-work-opt-in · r2 A2 pass HTTP-booted-server (flag boot): no NOT bound line, "32 flow(s) 21 bound" (19 on the default boot), _status bound:true for both; showcase_scheduled_digest completed every 60 s from 07:36:00Z (five runs to 07:40:00Z, trigger.type schedule)
    automation.screen-flow-roundtrip · r7 A0 pass browser-live+HTTP-booted-server: served /_console row-action click -> one POST /api/v1/actions/showcase_task/showcase_bulk_reassign {recordId,params:{}} -> 200 status:"paused", screen.nodeId "collect"; 0 POSTs to /automation/*/trigger; GET /automation/showcase_reassign_wizard/runs/:runId -> status paused, steps start:success,collect(screen):success (runs run_4b7b77c9…, run_7c303a37…, two fresh loads)
    automation.screen-flow-roundtrip · r7 A1 pass HTTP-booted-server: GET …/runs/:runId/screen -> 200 {runId, screen:{nodeId:"collect", title:"New Assignee", fields:[{name:"new_assignee",type:"text",required:true}]}} (x2, and again on the cancel-parked run)
    automation.screen-flow-roundtrip · r7 A3 pass browser-live+HTTP-booted-server: Submit sent POST …/runs/:runId/resume {inputs:{new_assignee:"qa-g6-r2-…@example.com"}} -> 200; dialog count 0; run re-read status completed, steps …,apply:success (x2)
    automation.screen-flow-roundtrip · r7 A4 pass HTTP-booted-server: GET /api/v1/data/showcase_task/8rWxGV6PsV4sJ4EY -> record.assignee == the submitted value (r1 and r2 values each matched; authoritative read, not the grid)
    automation.screen-flow-roundtrip · r7 A5 pass HTTP-booted-server: on a browser-launched paused run, POST resume {inputs:{}} -> 400 INVALID_SCREEN_INPUT "Screen field "new_assignee" is required"; also {} and {new_assignee:""} -> 400; run re-read paused (steps end at collect); later valid resume -> 200, run completed, task assignee = value (x2). N0 holds
    automation.screen-flow-roundtrip · r7 A6 pass browser-live+HTTP-booted-server: dialog buttons [Cancel, Submit, Close]; Cancel -> 0 dialogs, only the launch POST sent; run re-read paused at collect, task assignee unchanged; /screen 200 same contract; valid resume -> 200, run completed, task assignee = value (x2). N1 holds
    automation.setup-packaged-automation-board · r10 A0 fail HTTP-booted-server+browser-live: GET /api/v1/meta/app?id=setup group_apps holds nav_packages, nav_packaged_actions (type component, componentRef automation:packaged-actions, label "Packaged Actions"), nav_marketplace_installed — 0 occurrences of nav_packaged_automation; sidebar shows "Packaged Actions" (0 "Packaged Automation" links), click lands /component/automation/packaged-actions; typed retired URL …/component/automation/packaged redirects to /studio/com.example.showcase/metadata/flow (x2). Stale spec: #28785 (ADR-0146 D11/D4)
    automation.setup-packaged-automation-board · r10 A1 fail browser-live+HTTP-booted-server: the served Setup page has 0 packaged-flow-* rows (flows half retired by #28785); actions section renders (140 rows). Replacement: Studio › Automations rail lists 32 flows = GET /automation/_status 32, only showcase_approver_bindings enabled:false, 2 schedule flows unboundCause deployment_policy. Stale spec
    automation.setup-packaged-automation-board · r10 A2 fail browser-live+HTTP-booted-server: no flow switch on the Setup page (retired). Replacement does what the clause wants: Studio auto-ledger-switch on showcase_urgent_task_alert -> POST …/toggle 200 enabled:false; after full reload aria-checked=false, data-reading off, "Off for this deployment"; sys_metadata_activation row active:false; _status enabled:false; flipped back -> active:true (x2). Stale spec
    automation.setup-packaged-automation-board · r10 A3 fail browser-live: Packaged actions page has 0 Clone controls and 0 "New similar" (the actions half the charter protects HOLDS); "every flow row carries Clone" has no rows to read — flow copy moved to Studio's ⋯ › New similar (auto-item-new-similar). Stale spec
    automation.setup-packaged-automation-board · r10 A4 pass browser-live: with a flow clone present (qa_g6_s1_clone in com.example.showcase.custom), the served Packaged actions page has no customized/diff/cloned-from/drift/lineage text and 0 lineage/drift/diff/cloned testids; Studio's view of the clone shows none either (x2)
    automation.setup-packaged-automation-board · r10 A5 fail browser-live+HTTP-booted-server: no flow toggle on the Setup page (retired). Admin cell on the replacement: Studio switch on showcase_notify_owner -> 409 DELETE_RESTRICTED, sentence naming showcase_task_done_notify_owner rendered verbatim in data-testid=studio-refusal — but NOT role=alert (getByRole('alert') = 0) and under the tab bar, not beside the switch. Member cell: Setup 403 page, Studio "You need Studio access"; API toggle -> 403 "Enabling or disabling an automation flow requires the manage_metadata capability." (exact). Stale spec
    automation.setup-packaged-automation-board · r10 A6 fail browser-live: the page's clone dialog is retired. Replacement does what the clause wants: Studio New similar opens prefilled (…copy / "… (copy)"); submit disabled with name empty, label empty, both empty; Bad-Name! -> POST …/clone 400 VALIDATION_FAILED, role=alert auto-new-similar-refusal "Invalid flow definition: name: Invalid string: must match pattern /^[a-z][a-z0-9_]*$/", dialog stays open; legal clone -> 200, notice "Saved the copy … in package com.example.showcase.custom. The copy is switched off …" (x2). Stale spec
    automation.setup-packaged-automation-board · r10 A7 pass browser-live+HTTP-booted-server (posture recorded, fresh-context member minted invite-first, form sign-in, sys_member role member): /component/automation/packaged-actions renders "You don't have access to this app" (GET /meta/app/com.objectstack.setup 403 PERMISSION_DENIED), 0 switches; Studio flow URL renders "You need Studio access to open Studio"; sys_metadata_activation read 403; GET /automation/_status 200; GET /meta/action 200; action toggle POST /actions/_activation/showcase_task/showcase_archive_task -> 403 manage_metadata sentence
    automation.flow-authoring-refusals-v18 · r5 A1 pass cli-spawned (os validate --json, scratch app, one arm per run): every arm exit 1 — value token {record.owner} and text token {record.name} -> expression-invalid at flows[32].nodes[1].config.fields.owner / .config.title (names CEL envelope / (double-brace record.name template)); record.x in autolaunched no-entrance flow and user.id -> expression-invalid at .config.recipients (names current_user.id); $x screen field, outputVariable:42, notify bogusKey, dangling edge (edges.2.target), repeated edge (edges.2), job retryPolicy.maxRetry (did-you-mean maxRetries), try_catch retry.maxRetry, anonymous armed-rateLimit endpoint -> runAs:'system' flow (apis.3.authRequired, ADR-0138 D2b) -> STACK_SCHEMA_INVALID with the contract sentence at the located path. os build corroborates (bogusKey exit 2, record.x exit 1 "Author-time rules failed … expression-invalid", control exit 0). N0 holds: no arm validated clean
    automation.flow-authoring-refusals-v18 · r5 A2 pass HTTP-booted-server (boot of the scratch app declaring authRequired:false qa_anon_api -> tenant name qa_anon_target): PUT /api/v1/meta/flow -> 422 INVALID_METADATA with located issues for value token, text token, $x, outputVariable 42, bogusKey, dangling edge, repeated edge, try_catch retry.maxRetry, and qa_anon_target runAs:system (issue path "runAs"); each GET -> 404. record.x and user.id arms -> 200, GET 200. PUT /meta/job and /meta/api -> 403 NOT_CREATABLE (x2, second pass with fresh names)
    automation.flow-authoring-refusals-v18 · r5 A3 pass cli-spawned+HTTP-booted-server: os validate exit 0, 0 warnings on the planted flow, for CEL envelope {dialect:cel,source:record.owner}, notify title "Case (double-brace record.name template)", get_record filter {owner_id:'{current_user_id}'}; PUT each -> 200, GET 200 (x2)

    cli

    item · rev clause verdict layer · text evidence
    cli.scaffold-first-run · r7 A0 pass cli-spawned + HTTP-booted-server (PRE-RELEASE variant): 74 subject tarballs (pnpm pack, scripts ignored) on local verdaccio 6.11.1 :41671 with no uplink for @objectstack/* / create-objectstack (GET /create-objectstack 404 before publish); repo bin create-objectstack.js qa-first-run -t blank --skip-skills exit 0, pnpm install ran, no "Dependency installation failed", node_modules/@objectstack/cli 18.2.0; lockfile integrity of all 60 @objectstack/* entries == local tarball sha512 (60/60); npm run validate 0, npm run build 0; npx os start --artifact ./dist/objectstack.json --port 41672 health 200 {"status":"ok","version":"18.2.0"} on try 7 of 30, /api/v1/ready 200 {"status":"ready","state":"running"}
    cli.scaffold-first-run · r7 A1 pass cli-spawned: variants=[blank] (the one template in template-registry.ts): validate exit 0, build exit 0 → dist/objectstack.json 2.5 KB, over the subject's runtime (integrity-proven install above)
    cli.scaffold-first-run · r7 A2 pass HTTP-booted-server: os start --artifact of the scaffold booted healthy, server.log has no protocol refusal line ("✓ Server is ready", Mode production); scaffold pins 18.2.0 == candidate tarballs, so no RC-window skew applies
    cli.scaffold-first-run · r7 A3 pass cli-spawned: 18-line scaffold without --skip-skills (qa-skills): "Installing AI skills … from @objectstack/skills@18.2.0", "Found 11 skills in node_modules"; .claude/skills SKILL.md dirs (sort -u) == node_modules/@objectstack/skills/dist/skills SKILL.md dirs, 11 = 11, SET-EQUAL, 0 symlinks, no .agents left, no dogfood-verification. GitHub-path probe on a fresh skills-probe scaffold: npx -y skills add ‹checkout›/skills --all --copy (skills 1.7.4) exit 0, installed 11 == curated skills/ 11, SET-EQUAL; repo-root --list "Found 11 skills", dogfood-verification hits 0. N0 held
    cli.dev-automigrate-policy · r8 A0 pass cli-spawned (os dev, scratch app, config-declared sqlite qa_main + datasourceMapping [{default:true, datasource:'qa_main'}], autoMigrate 'safe'): three drifts staged (nn_field storage.notNull dropped = needs_confirm; doomed removed = destructive; legacy qaam_item_uq_field_unique planted on the stopped file = safe). Safe boot 1 log: exactly one [schema-drift] auto-reconciled replace_unique_index on qaam_item.uq_field, nn_field and doomed still warned; qaam_item dump 6793ff21f5b775e7 → 1017b7100dcac006 (legacy index gone, tenant-scoped index kept). Safe boot 2: no auto-reconciled line, dump unchanged (converged)
    cli.dev-automigrate-policy · r8 A1 pass cli-spawned: three boots over the staged drift — autoMigrate 'off', key omitted, 'off' again: each printed the same three [schema-drift] warnings (line sets diff-identical across boots), 0 auto-reconciled lines; full-schema dump 33d4895c3ad4976b and qaam_item dump 6793ff21f5b775e7 byte-identical before boot 1 and after boot 3 (legacy index still present). N1 held
    cli.dev-automigrate-policy · r8 A2 pass cli-spawned (NODE_ENV=production os serve objectstack.config.ts, OS_AUTH_SECRET + 64-hex OS_SECRET_KEY, legacy index re-planted, autoMigrate 'safe' declared): log carries verbatim [schema-drift] autoMigrate='safe' is ignored under NODE_ENV=production — schema is never auto-altered in production. Run 'os migrate' deliberately., the three drift warnings, 0 auto-reconciled; "✓ Server is ready", Mode production; dump before == after (legacy index kept)
    cli.dev-automigrate-policy · r8 A3 pass cli-spawned: destructive orphan doomed and needs_confirm nn_field warned on safe boot 1 and again on safe boot 2, never applied (dump still carries doomed text and nn_field text not null). schemaMode gate, both sides on one side datasource qa_side (autoMigrate 'safe', legacy qaam_side_code_unique planted): schemaMode 'external' boot → no drift line for qaam_side, side.db dump unchanged (legacy index kept); same config with schemaMode 'managed' → [schema-drift] auto-reconciled replace_unique_index on qaam_side.code, index gone. N0 held
    cli.migrate-meta-write-stored-and-all · r3 A3 pass cli-spawned: minimal stack (one object, no plugins/jobs/agents) os migrate meta --from 17: "427 manual change(s) require your judgment" + one line "21 more manual change(s) not listed: their surfaces are absent from this stack (run with --all to list them)."; --all: "21 manual change(s) whose surfaces are absent from this stack (listed by --all)" with 21 "absent: nothing is declared under …" lines (0 in the default run); --json todos 448, absentTodos 21
    cli.migrate-meta-write-stored-and-all · r3 A4 pass cli-spawned: scratch DB (booted once, stopped) with two sys_metadata flow rows — get_record filter {id:'{record.id}'} and CEL control {dialect:'cel',source:'record.id'}; os migrate meta --stored --json: scanned 2, canonical 1, skipped 1, row qa_meta_refused todos[0] conversionId flow-value-slot-template-dialect-refused path flows[0].nodes[1].config.filter.id; text mode prints TODO flow-value-slot-template-dialect-refused: "{record.id}" left as stored at flows[0].nodes[1].config.filter.id … and "☐ TODO: 1 site(s) in 1 row(s) are left as stored"; DB sha unchanged (preview)
    cli.validate-v18-authoring-verdicts · r2 A1 pass cli-spawned (scratch showcase copy, one plant per arm, reverted between arms): seed created_atx → validate exit 1 "defineSeed('showcase_task'): unknown field(s) … records[0]: created_atx … Did you mean 'created_at'?", build exit 2; record["typo"] → rule expression-invalid at objects[16].validations[1].condition "unknown field typo on showcase_project", build exit 1; datasource twice → STACK_CROSS_REFERENCE_INVALID "declared twice … stack.datasources[0] and stack.datasources[1]", build 2; cbp master_detail required:false → ObjectSchema.create('showcase_invoice_line') field invoice contradiction, build 2; object-grid defaultFilters → component-props-invalid at pages[19].regions[1].components[2].properties.defaultFilters, build 1; email {apiKey:'x'} → STACK_SCHEMA_INVALID "email: Unrecognized key(s) … apiKey" prescribing OS_EMAIL_API_KEY, build 2. Every arm: no "Validation passed", no dist/objectstack.json
    cli.validate-v18-authoring-verdicts · r2 A2 pass cli-spawned: controls — seed key created_at (stock), record.name == "record.typo" literal in the same rule, cbp master_detail required (stock), email: { provider: 'log' }: each validate --json exit 0 valid:true, text "Validation passed", build exit 0 with artifact
    cli.verify-derived-proof-families · r3 A1 pass cli-spawned (scratch showcase copy): os verify --X --json each exit 0, top-level hardFailures 0, family key present: referenceShapes (24 fields, 8 proven, 0 holes, 16 inconclusive), deleteBehaviors (9 proven, 0 contradicted), uniqueScopes (1 proven), parentWriteReach (2 objects proven, 180 held), dashboardWindows (17 widgets proven, 273 probes held) — summary.hardFailures 0 in all five

    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:devx#1 · os-warren · session_01JQwxbZx1yZXiTrFXKbSdFZ

    Close-out for this record (SKILL §5).

    One item failed, automation.setup-packaged-automation-board, on six clauses that pin the Setup › Packaged automation page #28785 retired: a stale spec whose re-point #28579 already carries (run evidence posted there). Six product defects observed beside the clauses are extracted; each passed the pm-dispatch filing gate (class ①). A seventh, os migrate meta --stored --json attesting clean over an absent database, is the same family as the open #28947 and was added there as a comment. The two PARTIAL items' blocked clauses and the checklist-accuracy lines are parked on the wave anchor.

    Extracted: #28579 · automation.setup-packaged-automation-board · acceptance[0] acceptance[1] acceptance[2] acceptance[3] acceptance[5] acceptance[6] (stale spec, existing re-point card)
    Extracted: #29130 · access-security.share-link-landing-page · observed beside the clauses
    Extracted: #29133 · automation.screen-flow-roundtrip · observed beside the clauses
    Extracted: #29134 · cli.scaffold-first-run · observed beside the clauses
    Extracted: #29135 · ai.mcp-http-surface · observed beside the clauses
    Extracted: #29137 · api-backend.bulk-write-contract · observed beside the clauses
    Extracted: #29139 · automation.setup-packaged-automation-board · acceptance[5]
    Extracted: #28947 · cli.migrate-meta-write-stored-and-all · observed beside the clauses (comment on the existing card)
    Parked-on: #27377 · api-backend.action-activation-door-contract · acceptance[4] blocked (fixture, knownGaps[3]: metadata-plane 503 arm has no stock door)
    Parked-on: #27377 · approvals.email-action-token-door · acceptance[0] acceptance[1] acceptance[3] acceptance[4] acceptance[5] blocked (fixture, knownGaps[0]: no production token producer)
    Parked-on: #27377 · 25 checklist-accuracy findings and 5 pin-promotion candidates (anchor comment)
    

    Observations below the filing gate stay in this record only: the bounded-probe warning's remedy "scope by parent_object" does not end the truncation when one parent object alone holds more than 2000 rows (log advice only); an admin may attach a file to a record of a read-only federated object (the join row is in the primary DB); stock showcase notify flows end failed on a task with no assignee (the notify contract working on optional data); MCP get_record with an empty id says "Record not found" (wording only).

    No security finding.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions