Skip to content

QA run · touched:8e3d4a54 (25/52) · c4196099 · 2026-11-13 · 22 PASS / 1 PARTIAL / 2 FAIL / 0 BLOCKED / 27 NOT-RUN #33093

Closed

Description

@objectstack-fleet

qa#3 · os-tesla · session_01L9qibw6yXcLi9aRRBpPT4W

Slice C-browser of qa#3's round 2 part C: the delta 8e3d4a5 → c419609, mixed/browser surface, 25 of 52 items; C-api is #33092.

Environment fingerprint

Scope

Selector touched:8e3d4a54 at c419609 → 52 items; this record is the 25 of items-C-browser.md. Revisions (all equal to the resolver's): inbox-metadata-actions 8 · dynamic-approver-routing 6 · record-page-decisions 5 · flow-node-type-matrix 14 · flow-error-handling 7 · flow-reference-package-resolution 2 · phone-signin-surfaces 3 · auth-method-matrix 10 · admin-lifecycle-operations 10 · session-list-revoke 4 · api-key-ui-lifecycle 5 · self-service-profile-password 3 · org-membership-team-management 6 · oauth-app-consent-loop 3 · linked-accounts-social 5 · identity-import-wizard 3 · first-run-owner-bootstrap 5 · self-signup-gate 8 · email-verification-loop 6 · workspace-org-switch 6 · implicit-account-linking-ownership 5 · sso-provider-groups-claim 1 · flow-connector-picker 5 · form-dirty-guard 7 · flow-status-matches-runtime 5. No P0 item in this slice.

Derived item verdicts

verdict items
FAIL (2) approvals.record-page-decisions (A4, stale spec — existing re-point card card-p2-1.md) · studio-authoring.flow-status-matches-runtime (A1, stale spec — existing re-point card card-browser-5.md)
PARTIAL (1) identity-auth.auth-method-matrix (N2 blocked(dependency): magic-link and passkeys have no UI, objectui#4179 closed not_planned; the S4 SSO half and S5 device variant blocked(environment) as knownGaps)
PASS (22) every other item

The delta, driven live

  • fix(plugin-auth): a directory deactivation revokes the user's API keys, and no one restores them #33075 (c419609) — a directory deactivation revokes the user's API keys, and no one restores them: holds. SCIM credential minted (Cache-Control: no-store); users provisioned through POST /api/v1/auth/scim/v2/Users (source: idp_provisioned), given a local password, each minting keys that read 200. Five deactivation spellings — SCIM PATCH active:false, DELETE /Users/{id}, PUT active:false, Azure-style pathless PATCH {active:false}, and the string "False" — each stamp every key row revoked:true, revoked_reason: scim_deactivated (an owner-revoked key is restamped) and the next request is refused 403 API_KEY_REVOKED (the ruled [Decision] API key 的生命周期与存放:到期上限与不活动失效取多长,key 继续放自有 sys_api_key 还是迁到 @better-auth/api-key(#23287 裁决 C′ 的留项) #23367 shape; only an unknown key is 401) naming the new-key road; every session row is deleted, the old bearer reads 401, sign-in answers 403 BANNED_USER details.cause: scim_deactivated. Re-activation (SCIM PATCH active:true, a SCIM re-POST re-linking through the tombstone, and import-users upsert) lifts the ban only: every old key stays revoked. Every restore door refuses — owner and admin restore_api_key 403, owner and admin PATCH {revoked:false} (with or without revoked_reason:null) 403, revoked_reason not editable 403, service-account/restore-key 404. A later admin set-user-password restamps only unrevoked keys (admin_password_reset) and leaves scim_deactivated rows as they are. A key minted after re-activation reads 200 and only the owner's rows; two other users' keys read 200 after every step. Control: an admin ban does not share the revocation (by design per the commit) — the key reads 401 while banned, its row stays revoked:false, and it works again after unban. A wrong SCIM bearer and a member's session bearer are both 401.
  • feat(approvals): a submitter's agent can recall and resubmit its own approval request over MCP (#32993) #33076 (9df85e1) — MCP recall / resubmit by the submitter's agent: holds. list_actions lists approve/reject/reassign/send_back/recall/resubmit on sys_approval_request, each aiExposed and requiresConfirmation. Recall: non-submitter unconfirmed 428 ACTION_CONFIRMATION_REQUIRED, confirmed 403 "Only the person who submitted this approval request can recall it…" (still pending, no action row); submitter unconfirmed 428 (nothing recorded), confirmed → recalled with exactly one [recall, Mei] row. Resubmit on a returned request: non-participant 404 REQUEST_NOT_FOUND; submitter unconfirmed 428, confirmed → round 2 pending, the run completes after approval; a participant on another pending request: resubmit 409 INVALID_STATE, recall 403.
  • fix(automation): a flow's answer also names the read-only fields its map items and resumed subflow children dropped (#33019) #33068 (e003ebb) — the flow answer names read-only fields dropped in map items and resumed subflow children: holds. On invoice tax_rate (readonlyWhen on paid invoices): the map answer carries droppedFields [{object: showcase_invoice, fields: [tax_rate], reason: readonly_when}] folded once, value unchanged; a resumed subflow child carries the same entry whether the resume names the parent or the child run id, and the parent's subflow step carries it; a draft-invoice control has no droppedFields member. (A static readonly write is refused at registration, 422 flow-update-readonly-field, so the probe needs a readonlyWhen field.)
  • fix(plugin-form): the modal and drawer record forms save a picked permission-set holder's package; pin the create form's seed rules #32931 (b232220) is plugin-form's modal/drawer holder pick; no C-browser item drives it directly beyond form-dirty-guard (pins green, live legs pass). The C-api slice carries its items.

Fails — reproduction rule and disposition

approvals.record-page-decisions · A4 — stale spec, existing re-point card card-p2-1.md (unchanged since ee9ad83). A contributor-bound member who reads project tyLQ-4kFq15pIZCu but is not a participant of its pending exec_review request: POST /api/v1/approvals/requests/areq_e4d9df94…/approve {} twice → 404 {"code":"REQUEST_NOT_FOUND"}, request stays pending; the clause expects 403 FORBIDDEN. The read gate holds (the member's approvals read is empty, the page draws 0 declared actions); the override-menu half passes.

studio-authoring.flow-status-matches-runtime · A1 — stale spec, existing re-point card card-browser-5.md. A writable package's schedule flow (interval 60000) on a default boot: _status reason "disabled by deployment policy — …" with unboundCause: deployment_policy; the rail and header titles read the console's worded sentence ("This deployment keeps scheduled work that comes with a package switched off…") and the verbatim reason is the Problems note's hover title, by design in describeFlowRunStatus (flow-problems.ts). Two page loads.

Off-clause probes (rule 8)

Each runner kept a probe budget after every item (2–10 min, the #33075 and #33076 drives inside it); per-item minutes and doors are in the probe log (record-C-browser-clauses-1.md). No product defect found; no card drafted.

Observed and not carded (low, for the seat to weigh): a cross-user unlink by a caller holding a single account answers FAILED_TO_UNLINK_LAST_ACCOUNT (the last-account check runs before ownership — misleading wording, row untouched); cross-user OAuth get-consent / delete-consent answer 401 rather than 403/404; an import-users upsert overwrites a directory-managed user's name; setting a SCIM user's password moves sys_user.source from idp_provisioned to env_native; on the unbound schedule flow the header enable switch is green "Switched on" beside "Not running here"; a flow authored status:'draft' is enabled and bound (by design per the boot log — "Created Off" means obsolete); approval / wait / screen steps log success while the run reads paused; an OIDC provider's overflow menu also offers "Edit SAML Settings"; the admin revoked sessions view keeps the subtitle "Active user sessions". An operator-registered OIDC provider asserting a verified address implicitly links and signs in to that user, platform admin included — the item's A1 design (a trusted provider vouching for a verified address), recorded, not scored.

Parked-on #32908

Fixture gaps: SSO just-in-time sign-in plus SCIM deactivation (SCIM adopts an SSO account only on a verified domain, which needs a DNS TXT record — OS_SSO_DOMAIN_VERIFICATION); the auth-method-matrix S4 SSO half and S5 device variant; magic-link / passkeys UI (objectui#4179 not_planned); users from admin create-user carry PASSWORD_RESET_REQUIRED on every data read until they change their password — fixtures should say to clear it; a local OIDC provider needs its origin in OS_TRUSTED_ORIGINS (else 400 SSO_REGISTER_FAILED "not publicly routable"); the showcase self-ping connector dials :3000 on a --no-compile boot (flow-node-type-matrix connector ping).

Per-item checklist-accuracy notes follow.

approvals.inbox-metadata-actions

  • A1.verify: "the dev admin on that same pending request shows 'Override Recall'" holds only once the admin's own slot on EXP-2001 is spent (the fixture routes the admin's manager group there); with the slot open the label is plain "Recall" (approvalOverride.ts isOverrideDecision, override-only viewer). Measured both states.
  • A3.verify names /api/v1/approvals/requests/:id/approve|remind as the click's network literal; the console posts /api/v1/actions/sys_approval_request/approval_* (action door, objectstack#27611), which forwards to that route. Stale literal, not a defect.

approvals.dynamic-approver-routing

  • S3 says the inbox row navigates to /apps/APP/sys_approval_request/record/ID: confirmed (APP = com.example.showcase).
  • The run's step log shows co_sign [approval] with status 'success' while the run reads 'paused' (both chains). This was noted, not scored: the clause reads run status, which is correct.

approvals.record-page-decisions

  • S5/S6/A1/A2 name POST /api/v1/approvals/requests/:id/reject|approve as the wire. The bar posts the action door POST /api/v1/actions/sys_approval_request/approval_reject|approval_approve, with recordId = the request id and the comment under params.comment. Stale literal; the substance holds.
  • S2 "capture the read the bar ran on: GET /api/v1/approvals/requests?object=…&recordId=…": on first load the approvals arrive inside POST /api/v1/data/showcase_project/:id/page. The GET fires on the post-decision refresh. Read the viewer block directly.
  • S9's example budget (200000 on a fourth project) can trip the showcase's 120%-of-spend validation rule on Data Platform (spent 420000), which answers 400. Pick a budget above spent/1.2.
  • The member's page logs 404 GET /api/v1/meta/object/sys_business_unit and /sys_comment_reaction. This is not scored (same class as the A non-admin approver cannot open an approval request: GET /meta/object/sys_approval_request answers 404 since #32428, so the inbox row, ?request= links and the record panel lose Approve/Reject #32955 family, already known from earlier rounds).

automation.flow-node-type-matrix

  • S8 composite run screenshot (Studio Runs tab, showcase_fan_out_notify run_8e24b4c8): 'Failed', run-level error "Node 'fan_out' failed: parallel 'fan_out': branch failed — …", 'FAILURE Notify in parallel PARALLEL', nested '↳ BRANCH 1 SUCCESS Notify Owner NOTIFY' and '↳ BRANCH 2 FAILURE Slack Notify HTTP … ENOTFOUND hooks.slack.com'.
  • showcase_declarative_connector_ping (fired by every task create) fails connector_action(showcase_status_api.request) failed: fetch failed on this --no-compile boot: its frozen base URL dials :3000, which matches the known environment note. Not scored; connector_action was proven on two other connector flows.
  • The bogus-node trigger answers HTTP 400 with success:false. The clause names only success:false, which holds.
  • The run step log records approval/wait/screen steps with status 'success' while the run is 'paused' (e.g. dynamic approval co_sign, hold [wait]). The suspension is read from run status, as A2 does.

identity-auth.phone-signin-surfaces

  • On the OFF boot the Email label carries "*" (required). On ON it does not, because a phone can stand in for it ("Required when no email is given"). This is consistent with the product.

identity-auth.auth-method-matrix

  • /auth/config wraps its payload in data ({success, data:{emailPassword, features…}}). The steps read features.* as top level.
  • Also served under the prefix: GET /api/v1/auth/.well-known/openid-configuration → 200. A8 says the documents are "mounted at app root, not the /api/v1/auth prefix". Both answer, and the root one is the RFC location. This is a note only.
  • On a dev boot, phoneNumberOtp is advertised true with no SMS service (the log transport). S3 already explains this.

identity-auth.admin-lifecycle-operations

  • During impersonation, the admin's ORIGINAL bearer answers 401 on get-session and on data reads. The held session is refused, so a second admin session was needed to read sys_session.impersonated_by. S9 could say so.
  • S10's "a throwaway that OWNS a showcase row": showcase_task needs project (400 VALIDATION_FAILED otherwise).
  • On the single posture, create-user answers membershipCreated:false even though the user lands in the Default Organization's member list. That is the bootstrap join, not create-user's.

identity-auth.session-list-revoke

  • A0's "BOTH of A's sessions" assumes A holds exactly two. Here, user setup (create-user, then a forced change-password needing a sign-in) left A extra live sessions, so the count was 5, all A's own. Either start from a user with no prior sign-ins, or read the clause as "A's sessions only".
  • The admin revoked view keeps the object's subtitle "Active user sessions" and the "Sign out other devices" toolbar button. This is cosmetic.

identity-auth.api-key-ui-lifecycle

  • S4/S5 call the row action a PATCH … bodyExtra {revoked:true}. The console's row action actually posts to POST /api/v1/actions/sys_api_key/revoke_api_key (and restore_api_key) with {recordId, params:{}}. The PATCH door also works, and both were driven. Re-spell the step, or name both doors.
  • A user made by admin/create-user is mustChangePassword:true, so every data read answers 403 PASSWORD_RESET_REQUIRED until change-password. The fixture should say to clear that first.
  • Off-clause: the row menu offers "Edit" on sys_api_key, yet a PATCH of name is refused 403 "None of the submitted fields (name) are editable … Editable fields: revoked". This was not opened in the UI form, so it is a note only.

identity-auth.org-membership-team-management

  • S6 says remove-member revokes the live sessions. Measured: the old session answers 401 at once, with reason organization_membership_ended. The step is correct.
  • A1's "the gated operation" is not named. invite-member worked as a discriminator for member ↔ admin. Suggest naming it.

identity-auth.oauth-app-consent-loop

  • create-client's default grant_types is [authorization_code] only; A3's "access/refresh tokens" needs grant_types including refresh_token AND the offline_access scope at authorize. Without them the token answer has no refresh_token. The step could say so.
  • Cross-user get-consent / delete-consent answer 401 (not 403/404); a refusal either way, noted only.

identity-auth.linked-accounts-social

  • The UI unlink goes through the action door POST /api/v1/actions/sys_account/unlink_account (which reaches the auth unlink), not a direct POST /auth/unlink-account from the browser; the step text names the latter. The direct door also works (implicit-linking run: 200 {status:true}).
  • Fixture: a real social provider still needs real client credentials; the local OIDC provider stood in (as rev 5 records).

identity-auth.identity-import-wizard

  • The wizard's default match mode is "Always create new"; S4's upsert needs the operator to pick "Update if matched, else create" and tick Match on: Email (the Import button stays disabled until a match field is chosen). The step could say so.
  • An insert-mode import of an existing email answers row error "User already exists. Use another email." (IMPORT_ROW_FAILED) inside a 200. That is correct, recorded for the item's negatives.

identity-auth.first-run-owner-bootstrap

  • Same as the earlier run: S5 says "two document loads, to /_console/ and then /_console/home", but both document requests were /_console/, and /home is a client route after the reload. The contract (a full-document exit) holds.

identity-auth.self-signup-gate

  • As before, the invitation card heading is "Dev Admin invited you to join Default Organization", not "Accept your invitation". The link is as described.

identity-auth.workspace-org-switch

  • A5's "the UI surfaces an error" has no UI path: the forged call is API-only, and after it the console shows the organization picker (no active org), not an error. Scored on "never the foreign org".
  • The admin persona note says the walled admin "starts with none"; at c419609 the seeded admin already holds a "Default Organization" on a walled boot. create-user still needs the admin's active org set to the intended one.
  • A3's group write needs a project in the new org (showcase_task.project is required); the member's own org is seeded, so a Beta project exists.

identity-auth.sso-provider-groups-claim

  • fixtures.knownGaps is right that the issuer must be reachable; a local IdP additionally needs its origin in OS_TRUSTED_ORIGINS (the register door refuses a loopback discovery URL otherwise). Worth adding to the item's requires.
  • Observed: the OIDC provider's overflow menu also offers "Edit SAML Settings". Not scored; cosmetic.

integration-system.flow-connector-picker

  • A fixture flow for POST /api/v1/automation must declare a connectorConfig block on the node (connectorId/actionId/input). A config:{connectorId…} form is refused 422 INVALID_METADATA "a connector_action node requires a connectorConfig block". The item's fixture text does not say this.
  • By design (connector-input-fields.ts), the typed form shows no required asterisk for echo_upper's text, though the JSON-Schema required lists it. Noted so A2's "input fields match inputSchema" is not read as a gap.

records-forms.form-dirty-guard

  • The tab-reload beforeunload leg (S1's aside) was not driven (it is a native prompt; the pin's describe.each covers it at the component layer).

studio-authoring.flow-status-matches-runtime

  1. A flow authored over the API with status:'draft' is enabled and bound (_status {enabled:true,bound:true,status:'draft'}) and fires on a create (run_fcc42d7c). That is by design: the boot logs "draft flows still fire their triggers; set status: 'active' … or 'obsolete' to disable". "Created Off" therefore means status:'obsolete'; that is what the Studio switch writes (StudioDesignSurface toggleEnabled: active ↔ obsolete). The fixture text should say so for API authoring.
  2. On the default boot, the schedule flow's header shows the enable switch green, labelled "Switched on", beside "Status: Not running here". The switch reflects enabled (true, which _status agrees with); the status word and the dot carry the binding. I do not read it as N0's "green On", but a reader of N0 could, so the clause could name the status pill and dot as the surfaces it means.
  3. The launch entry showcase-scheduled-work-3778 has its own fixed port; this run used the same environment on runner port 41303.

Activity

  1. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    qa#3 · os-tesla · session_01L9qibw6yXcLi9aRRBpPT4W

    Probe log (rule 8)

    • approvals.inbox-metadata-actions: 6 min. Delta 9df85e1 (feat(approvals): a submitter's agent can recall and resubmit its own approval request over MCP (#32993) #33076), MCP POST /api/v1/mcp tools/call: Mei's list_actions lists approval_approve/reject/reassign/send_back/recall/resubmit on sys_approval_request, each aiExposed:true requiresConfirmation:true. On EXP-2001 (submitter Mei): Ada (non-submitter approver) run_action approval_recall unconfirmed → ACTION_CONFIRMATION_REQUIRED 428 "nothing was run"; confirmed → FORBIDDEN…
    • approvals.dynamic-approver-routing: 3 min, approvals route on probe request areq_23685f17… (left pending): next_reviewers as a bare string → 400 "takes a list of user ids (it is declared multiple: true)"; ['usr_nope'] → 400 "'usr_nope' names no user. Nothing was recorded…"; an undeclared extra key in outputs → 400 "decision output key(s) bogus are not declared on this node"; re-approving a finalized request → 409 INVALID_STATE …
    • approvals.record-page-decisions: 6 min. Delta 9df85e1 (feat(approvals): a submitter's agent can recall and resubmit its own approval request over MCP (#32993) #33076), MCP resubmit leg: FLks… budget 260000 → areq_51f006b5… pending. Admin POST /approvals/requests/:id/revise → status returned, run run_f6edce3f… paused at wait_revision [approval_revise]. MCP run_action approval_resubmit: Ada (not the submitter, not a participant of this request) confirmed → 404 REQUEST_NOT_FOUND (read-gate by design); the submitter (admin) unconfirm…
    • automation.flow-node-type-matrix: 8 min. Delta e003ebb (fix(automation): a flow's answer also names the read-only fields its map items and resumed subflow children dropped (#33019) #33068), live: (a) map: env flow qa_c1_drop_parent (map over showcase_invoice items → qa_c1_drop_child update_record tax_rate, a readonlyWhen field locked on paid invoices). Triggered on two paid invoices → success:true, answer droppedFields [{"object":"showcase_invoice","fields":["tax_rate"],"reason":"readonly_when"}] (one folded entry), the map step carries the same `drop…
    • automation.flow-error-handling: 4 min. Delta e003ebb (fix(automation): a flow's answer also names the read-only fields its map items and resumed subflow children dropped (#33019) #33068): see automation.flow-node-type-matrix probes (map and resumed-subflow droppedFields, both resume-id paths). Here I also confirmed that the probe's condition kept it inert for the other task creates (1 run only). Nothing found.
    • automation.flow-reference-package-resolution: 3 min. An env-level PUT /meta/flow/qa_a_child (a name the installed package holds) → refused "…provided by a managed package and is sealed against in-place edits. Declare an override from 'com.qa.addr-a.custom' or rename it…", so no env shadow can be made. A door trigger of qa_a_child itself → 200. An unknown flow trigger → 404 RESOURCE_NOT_FOUND. Nothing found.
    • identity-auth.phone-signin-surfaces: 2 min
    • identity-auth.auth-method-matrix: 3 min
    • identity-auth.admin-lifecycle-operations: 3 min
    • identity-auth.session-list-revoke: 4 min
    • identity-auth.api-key-ui-lifecycle: ~8 min (plus the delta, ~10 min)
    • identity-auth.self-service-profile-password: 2 min
    • identity-auth.org-membership-team-management: 3 min
    • identity-auth.oauth-app-consent-loop: 4 min. Code replay → 400 invalid_grant and the first access token introspects active:false afterwards (RFC 6749 §4.1.2 revocation); authorize with an unregistered redirect_uri → 302 /_console/login?error=invalid_redirect (no redirect to the foreign host); token with a wrong client_secret → 401 invalid_client. No finding.
    • identity-auth.linked-accounts-social: 4 min. W with ONE account (credential only) unlinking U's row → 400 FAILED_TO_UNLINK_LAST_ACCOUNT (the last-account check runs before ownership; message is misleading for a foreign row, row untouched; low, not carded). W with two accounts → 400 ACCOUNT_NOT_FOUND for U's row. accountId "" → 400. Re-link after unlink works (new row).
    • identity-auth.identity-import-wizard: 10 min, delta fix(plugin-auth): a directory deactivation revokes the user's API keys, and no one restores them #33075 through the import door. SCIM connection credential minted (POST admin/scim/mint-credential → 200, token once); SCIM POST /api/v1/auth/scim/v2/Users scim.u1@sso.example.test → 201 (sys_user.source idp_provisioned); user given a credential (admin set-user-password + change-password) and minted key osk_P-3b… (201; x-api-key read 200). SCIM PATCH active:false → key 403 API_KEY_R…
    • identity-auth.first-run-owner-bootstrap: 2 min
    • identity-auth.self-signup-gate: 2 min
    • identity-auth.email-verification-loop: 3 min. A real ev2 token with a tampered signature → 401 INVALID_TOKEN; an alg:none token naming ev2 with a far exp → 401 INVALID_TOKEN; ev2 still refused afterwards. The console login attempt for an unverified user sent no extra mail. No finding.
    • identity-auth.workspace-org-switch: 3 min. set-active to a non-existent id → 403 USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION; set-active by organizationSlug "default" → same 403. Under group with the active org cleared by the refusal, reads span only Alpha + Beta (never Default) and a fresh sign-in starts on Beta, so a write lands in Beta. Single posture: organization/create → 403 ORGANIZATION_CREATION_DISABLED for member and admin. N…
    • identity-auth.implicit-account-linking-ownership: 4 min. (1) IdP asserting email_verified:false for the verified admin's address → error=account_not_linked, no session (provider-side gate). (2) IdP asserting email_verified:true for admin@objectos.ai (verified) → implicitly links and signs in as the admin. That is A1's designed behaviour (an operator-configured provider vouching for a verified address), recorded, not a finding. Note: probe (2) …
    • identity-auth.sso-provider-groups-claim: 3 min. set with groupsClaim 123 → 400 INVALID_REQUEST "groupsClaim must be text"; unknown provider on set → 404; anonymous get → 401 UNAUTHENTICATED; whitespace " " clears like blank (null); extra key ignored. No finding.
    • integration-system.flow-connector-picker: 3 min. Switched mcp → slack and read each pick's action datalist (each re-derived, nothing stale carried over); no publish on the wire. Observation: the fixture was POSTed with status:"draft", and Studio's header still reads "Switched on" with the toggle on. _status agrees ({enabled:true, bound:false, status:"draft"}), so Studio and runtime say the same thing. I did not pursue it further (enabl…
    • records-forms.form-dirty-guard: 3 min. Stay-then-close again re-prompts (X → Keep editing → X → prompt → Discard); Escape and backdrop on a dirty form never close the form under the prompt (formStillOpen 1); the save path writes only the changed key ({"title":"Sales Prospect"}). Nothing found.
    • studio-authoring.flow-status-matches-runtime: 3 min. The draft-status leg (note 1). Between the switch flip and the publish, the header still read 'Switched off' and the rail did not move, which is consistent with the 'publish to apply' toast. The publish dialog lists exactly the one changed item with "~ Status". Nothing further found.

    Per-clause tables

    approvals.inbox-metadata-actions · rev 8 · PASS

    clause verdict evidence
    A0 pass Ada → My Pending → EXP-2001 Request cell → areq_c1f7c42e…: screenshot shows the request page, "Sign-off progress — 0 of 2 groups", "Waiting on Dev Admin · manager, Ada Auditor (demo) · finance", bar [Approve (filled violet), Reject (red), Reassign, Send back, Request info, Reply]. DOM main buttons = the same 6 (+Detai…
    A1 pass (1) own request: admin → Submitted by me → INV-1012 → [Approve, Reject, Reassign, Send back, Request info, Send reminder, Recall, Reply], flags {can_act:true,is_submitter:true,can_override:true}. (2) Ada on EXP-2001 → neither Send reminder nor Recall, flags {true,false,false}. (3) admin on the same EXP-2001 wh…
    A2 pass every (viewer, request) render matches its GET /api/v1/approvals/requests/:id viewer flags under the declared gates: Ada {T,F,F} → decisions+send back/request info+reply; admin {T,F,T} → same + Recall; admin {F,F,T} → only the can_override-ORed levers (approve/reject/reassign/recall); admin on INV {T,T,T} → all but Re…
    A3 pass EXP-DEMO (admin, request page) Approve → one dialog "Approve this request? Your approval is recorded…" → Confirm → POST /api/v1/actions/sys_approval_request/approval_approve {"recordId":"areq_890f8283…","params":{"comment":"qa c1 Approve"}} 200, toast 'Approved "EXP-DEMO"', re-read status approved, pending_approvers […
    A4 pass GET /:id/actions before→after: EXP-DEMO 1 [submit, Mei] → 2 (+[approve, Dev Admin, "qa c1 Approve"]); INV-1012 1 [submit, Dev Admin] → 2 (+[remind, Dev Admin, "qa c1 Send reminder"]); EXP-2001 (override-setup approve) 1 → 2 (+[approve, Dev Admin]) — exactly one row per action naming the actor
    N0 pass no button drawn against a denying flag in any of the 6 renders. Server agrees: Ada POST /approvals/requests/:id/remind → 403 FORBIDDEN "only the submitter may send reminders"; Ada POST /actions/sys_approval_request/approval_recall → 403 "Only the person who submitted this approval request can recall it…"; status pendi…

    approvals.dynamic-approver-routing · rev 6 · PASS

    clause verdict evidence
    A0 pass PATCH announcement title → 200; areq_86642c2e… pending lead_review, pending_approvers [admin], decision_output_defs [{key:'next_reviewers',type:'user',multiple:true,required:true}]. Request page /_console/apps/com.example.showcase/sys_approval_request/record/areq_86642c2e… → declared-action-approval_approve → one dial…
    A1 pass dialog Confirm with nothing picked: no POST sent, dialog stays open with "Next Reviewers is required". Server: POST /api/v1/approvals/requests/:id/approve {} → 400 VALIDATION_FAILED "decision output(s) next_reviewers are required to approve this request…"; {"outputs":{"next_reviewers":[]}} → same 400; action doo…
    A2 pass chain 1 dialog Confirm → POST /api/v1/actions/sys_approval_request/approval_approve {"recordId":"areq_86642c2e…","params":{"outputs.next_reviewers":["IKhRct9ZBdQdGBBFKO3s8Vva53sWcU0N"]}} → 200; stage-2 areq_2c76a217… pending co_sign, pending_approvers ["IKhRct9ZBdQdGBBFKO3s8Vva53sWcU0N"] — exactly the pick
    A3 pass chain 1 (1 pick): GET /api/v1/automation/showcase_dynamic_approval/runs/run_9ebff8d0… → status paused, variables['lead_review.next_reviewers'] = ["IKhRct9ZBdQdGBBFKO3s8Vva53sWcU0N"] (= POST body), stage-2 pending_approvers 1 entry (read after the restart, so the snapshot is durable). chain 2 (2 picks via the dialog, A…
    A4 pass chain 1: admin approves areq_2c76a217… → finalized:true, resumed:true, status approved; run_9ebff8d0… completed. chain 2: Ada (own token) approves → finalized:false, pending_approvers [admin]; admin approves → approved; run_109f047d… completed. Both runs paused → completed
    N0 pass no outputless approve succeeded on any door (A1)

    approvals.record-page-decisions · rev 5 · FAIL

    clause verdict evidence
    A0 pass /_console/apps/showcase_app/showcase_project/record/8bQ4… (lands on …/com.example.showcase/…): screenshot shows the bar ABOVE the record header: Approve (filled violet), Reject (red), Reassign, Send back, Request info, Recall, Reply. The header holds only Edit + ⋯. DOM: declared-action-approval_approve (bg-primary), a…
    A1 pass Reject click → exactly 1 dialog (screenshot): title "Reject", description "Reject this request? A rejection is final for every approver.", Comment textarea, Attachments drop zone, Cancel / Confirm. Confirm → exactly one POST between the click and the close: POST /api/v1/actions/sys_approval_request/approval_reject {"r…
    A2 pass reject: GET /approvals/requests/areq_f23f5f38… status rejected, /actions [submit, reject Dev Admin "QA C1 reject comment via record page"] (same on areq_f70c4993…). Approve on UjSa…: one dialog "Approve this request? Your approval is recorded…" → POST …/approval_approve {recordId:"areq_224a7dce…",params:{comment:"QA C…
    A3 pass editable side (tyLQ… manager_review, lock_record:false): Edit enabled, 8 "Double-click to edit" pencils, band "In approval · editable" (also on 8bQ4…, screenshot: sky clock pill). Approve from the bar → exec_review areq_e4d9df94… pending, lock_record:true, approver admin. After the decision's refresh AND after a reloa…
    A4 fail (stale spec — existing re-point card out/card-p2-1.md) member side, gate holds: the member's GET /api/v1/approvals/requests?object=showcase_project&recordId=tyLQ… → 200 {"data":[]}. Their record page (screenshot: "Mobile App" rendered, band "Locked for approval") holds 0 declared-action-* buttons. Forged POST /api/v1/approvals/requests/areq_e4d9df94…/approve {} as the mem…
    N0 pass one dialog, one POST, and the decision was recorded (A1, twice)
    N1 pass 0 inline-edit pencils and Edit disabled while exec_review (lockRecord:true) is pending, on refresh and on reload
    N2 pass the member's approvals read has no row, and their page draws no declared decision action

    automation.flow-node-type-matrix · rev 14 · PASS

    clause verdict evidence
    A0 pass GET /api/v1/automation/actions → 20 descriptors: approval approval_revise assignment connector_action create_record decision delete_record get_record http loop map notify parallel save_notice screen script subflow try_catch update_record wait (no start/end, by design). Palette: POST /automation/showcase_task_completed…
    A1 pass start: every run, e.g. showcase_task_completed run_e313b225 start success. end: that run completed (inquiry_purge's end:skipped not used). create_record: showcase_inbound_task_webhook run_289512f1 create_task success (signed t=,v1= hook → 202). update_record + screen: showcase_reassign_wizard run_429c96cf collect / …
    A2 pass screen: action door POST /actions/showcase_task/showcase_bulk_reassign → 200 status paused with screen payload 'New Assignee'; run_e80e7305 read paused, resumed (POST …/runs/:id/resume) → completed. Browser leg: Tasks list row menu "Reassign…" → dialog "Reassign Task · Step 1 of 1 · New Assignee *" → Submit → POST …/r…
    A3 pass every step printed from the ~17 captured runs carries a nodeType; no empty or missing value
    A4 pass loop: guard_reminder parent=loop_tasks region=loop-body. parallel: notify_owner / notify_watcher parent=fan_out_audiences region=parallel-branch (also parent=fan_out in showcase_fan_out_notify). try_catch: send_reminder parent=guard_reminder region=try; reminder_failed region=catch
    A5 pass (1) POST /api/v1/automation {nodes:[start, weird(type bogus_node), end]} → 200; GET /meta/flow/qa_c1_bogus_probe stored types start, bogus_node, end. (2) server log `2026-11-13T10:35:54.722Z WARN Flow 'qa_c1_bogus_probe' references node type(s) with no registered executor or descriptor — the unknown type names and the…
    N0 pass not silent: the WARN carries the phrase and both meta lists, the trigger answers success:false, and the run is failed with a NO_EXECUTOR step

    automation.flow-error-handling · rev 7 · PASS

    clause verdict evidence
    A0 pass task "QA-C1 resilient task" (project UjSa…, in_review, assignee admin) → POST /automation/showcase_resilient_sync/trigger {params:{taskId,title}} → 200 success:true after 7.18 s; run run_9dc09ef4 status completed; step guarded_push [try_catch] status success (by design, #7546)
    A1 pass run steps in log order: start · guarded_push · push [http] failure parent=guarded_push region=try ×4, each with error {code:NODE_FAILURE, message:"http: fetch failed — api.example.com: getaddrinfo ENOTFOUND api.example.com"} · record_failure [update_record] success parent=guarded_push region=catch. Every try step come…
    A2 pass retryAttempt on the region=try steps: 0,1,2,3 (four distinct values = maxRetries 3 + 1), each a failure. Cross-check: run durationMs 7155 ≥ 1000+2000+4000 ms of backoff
    A3 pass GET /data/showcase_task/:id → sync_status "failed", sync_error "Node 'push' failed: http: fetch failed — api.example.com: getaddrinfo ENOTFOUND api.example.com"
    A4 pass probe qa_c1_unhandled_probe: POST /api/v1/automation, package-unbound (~org), start record-after-create on showcase_task with condition title == "QA-C1 unhandled probe" → http POST https://api.example.com/v1/tasks → end; _status shows it bound:true record_change. Fired by creating that task (201). Run run_707ed125 sta…
    A5 pass Studio Runs tab, handled (com.example.showcase/showcase_resilient_sync): the screenshot shows 'Completed 10:37:12 AM 7.2s', 'run run_9dc09ef4… · trigger manual', 'SUCCESS Push with retry TRY_CATCH 7.2s', a nested '↳ TRY' with four red 'FAILURE Push to CRM HTTP … http: fetch failed — api.example.com: getaddrinfo ENOTFO…
    A6 pass server log (trigger-fired): `2026-11-13T10:37:36.114Z ERROR Trigger-fired run of flow 'qa_c1_unhandled_probe' failed (trigger 'record_change') — no caller holds this result and nothing retries the run; the terminal failure is recorded in the flow's run history, and the run's failure envelope is in this record's meta. …
    N0 pass the unhandled run is failed and its run-level error is populated (A4)
    N1 pass showcase_batch_reminders run_808dc7c4 (2 iterations): iteration 1's try (send_reminder region=try success) has NO catch step. Only iteration 2, whose notify failed ('at least one recipient is required'), ran reminder_failed region=catch
    N2 pass the handled run carries 4 region=try failure steps, not the [start, container(success), catch] silence shape
    N3 pass the container's success is recorded as designed; not filed

    automation.flow-reference-package-resolution · rev 2 · PASS

    clause verdict evidence
    A0 pass POST /automation/qa_b_parent/trigger (twice) → 400 FLOW_FAILED "Node 'call' failed: subflow 'qa_a_child' failed: The flow "qa_a_child" is held outside the declared dependency closure of package "com.qa.addr-b": package "com.qa.addr-a". Declare a dependency on the package that holds it (ADR-0141)…". Both runs failed [s…
    A1 pass POST /automation/qa_b_env/trigger → 400 FLOW_FAILED "…subflow 'qa_env_child' failed: The flow "qa_env_child" is held outside the declared dependency closure of package "com.qa.addr-b": the environment (an item authored in this environment, bound to no package)…". The run failed at call, and qa_env_child had 0 runs a…
    A2 pass POST /automation/qa_env_child/trigger (a door run; the caller has no package) → 200 success:true, flowLabel "QA env child"; run run_5b54b910 completed [start:success]
    A3 pass qa_b rebuilt as 1.0.1 with manifest.dependencies {"com.qa.addr-a":"^1.0.0"} and reinstalled ("Package installed into the running kernel", Version 1.0.1); GET /packages shows that dependency. qa_b_parent trigger → 200 success:true; run run_25dddf5f completed; qa_a_child runs 0 → 1 (run_05bc3323 completed)
    A4 pass (pin) pins green at c419609: flow-package-scoped-resolution.test.ts 43✓
    N0 pass without the dependency, qa_b_parent did not run qa_a_child (A0: 0 child runs over 2 attempts)
    N1 pass qa_b_env was never served qa_env_child: it failed at call both before AND after qa_b's dependency change (still 400 naming the environment after the 1.0.1 reinstall); qa_env_child's only run is the direct door trigger

    identity-auth.phone-signin-surfaces · rev 3 · PASS

    clause verdict evidence
    A0 pass ON boot, before any browser work: admin GET /data/sys_user filter phone_number=+8613800138000 → 200, a single row {id usr_showcase_phone_demo, name "Mei Phone (demo)", email phone.demo@example.com, phone_number +8613800138000, phone_number_verified false}. The OFF boot reads the same row.
    A1 pass Three screenshots on the ON boot. (1) All Users list: the headers are Name / Email / Phone Number / …, and the row reads "Mei Phone (demo) · phone.demo@example.com · +8613800138000". (2) Record detail: highlights Email / Phone Number "+8613800138000" / Email Verified No. (3) Create User → Confirm → result dialog "User…
    A2 pass The action posted POST /api/v1/actions/sys_user/create_user {params:{email, name, phoneNumber:"+8613900139001", password, mustChangePassword:true}} → 200, data.user.phoneNumber +8613900139001. Admin read of sys_user: phone_number +8613900139001, and the phone_number_verified column is present (false).
    A3 pass ON: /auth/config features.phoneNumber true. The Create User dialog labels are Email / Phone Number / Name / Password (leave empty to generate) / Require Password Change On First Login / Permission Set (screenshot). OFF: features.phoneNumber false. The dialog labels are Email* / Name / Password… / Require… / Permission…
    N0 pass The seeded persona is present on both boots.
    N1 pass No phone param while the flag is false (A3).

    identity-auth.auth-method-matrix · rev 10 · PARTIAL

    clause verdict evidence
    A0 pass /auth/config by boot (twoFactor / phoneNumber / phoneNumberOtp / deviceAuthorization / oidcProvider / sso / socialProviders): ON true / true / true (the dev log transport) / false / true / false / []. OFF false / false / false / false / true / false / []. PROD false / true / false / false / true / false. Every boot: e…
    A1 pass admin-created am1@ (forced change cleared). POST /sign-in/email → 200, with set-cookie better-auth.session_token and a set-auth-token bearer. get-session (cookie) → 200 am1@c2.test. POST /sign-out → 200 {success:true}, clearing session_token / session_data / dont_remember (Max-Age=0). get-session with the old cookie →…
    A2 pass ON: a user created with phone +8613900139001 and a password. POST /sign-in/phone-number {phoneNumber, password} → 200 {token, user}. get-session → phoneuser@c2.test, phoneNumber +8613900139001. Wrong password → 401 INVALID_PHONE_NUMBER_OR_PASSWORD.
    A3 pass PROD: POST /phone-number/send-otp → 400 {"code":"SMS_SERVICE_REQUIRED","message":"Phone verification codes are unavailable: this deployment has no configured SMS delivery service…"}. A second number gives the same. /auth/config phoneNumberOtp false (phoneNumber true). Contrast, ON (development): send-otp → 200 "code s…
    A4 pass Login screenshots. OFF: the input is type=email "name@example.com", with Sign In and no phone or verification-code affordance. ON: "Email or phone number" / "name@example.com or +1 555 000 0000", Sign In, and "Sign in with verification code". PROD: a phone-capable identifier, with NO verification-code button (OTP is n…
    A5 pass OFF boot: POST /sign-in/phone-number, /phone-number/send-otp, /phone-number/verify, /two-factor/enable (authed), /two-factor/verify-totp (authed), /device/code and /sign-in/magic-link → each 404 with an empty body (route absent). Admin create-user with phoneNumber → 400 "Phone numbers require the phoneNumber auth plug…
    A6 PIN (also driven live: pass) ON: /two-factor/enable {password} → 200 {totpURI, backupCodes}; verify-totp with a computed TOTP → 200 (activated). Password sign-in → 200 {"twoFactorRedirect":true,"twoFactorMethods":["totp"]} and a better-auth.two_factor cookie. get-session before the challenge → 401 UNAUTHENTICATED; a protected data read → 401. ver…
    A7 pass (pin) pins green at c419609: oidc-authorization-code-flow.dogfood.test.ts 3✓, two-factor-lockout.dogfood.test.ts 5✓, phone-otp-no-sms-service-refusal.test.ts 5✓, change-email-delete-user-wiring.test.ts 20✓
    A8 pass GET /.well-known/openid-configuration → 200 and GET /.well-known/oauth-authorization-server → 200, both at the app root. issuer http://localhost:41321/api/v1/auth. authorization_endpoint …/api/v1/auth/oauth2/authorize (GET 200), token_endpoint …/oauth2/token (POST {} → 415, mounted), userinfo_endpoint …/oauth2/userinf…
    A9 pass Anonymous POST /change-email → 401 UNAUTHORIZED. Authed change-email {newEmail am1-new@} → 200 {status:true}. The address did NOT flip (sys_user still am1@, no am1-new@ row): the confirmation goes to the new address, per the ledger note. Authed POST /delete-user → 404 with an empty body. Anonymous → 401 UNAUTHORIZED. …
    N0 pass No 2xx on any disabled method (A5).
    N1 pass A loud 400 SMS_SERVICE_REQUIRED (A3).
    N2 blocked(dependency, objectui#4179 closed not_planned) Magic-link and passkeys have no UI. Not ticked.
    N3 pass Neither features.magicLink nor features.passkeys appears on any of the three boots.
    N4 pass The discovery endpoints resolve against the live mount (A8).

    identity-auth.admin-lifecycle-operations · rev 10 · PASS

    clause verdict evidence
    A0 pass S0: /auth/config data.features.admin true. Admin ban-user {userId, banReason:"qa c2 lifecycle"} → 200; sys_user reads banned true, ban_reason "qa c2 lifecycle". The banned user's POST /sign-in/email → 403 {"code":"BANNED_USER","message":"You have been banned…"}. unban-user → 200; the row reads banned false, ban_reason…
    A1 pass Driven live, and also pinned. set-user-password {newPassword} → 200 (revoked sessions 1, apiKeys 0, complete). The new password → 200; the old one → 401 INVALID_EMAIL_OR_PASSWORD.
    A2 pass Driven live, and also pinned. create-user {email, password:"Explicit…", generatePassword:true} → 200; the response carries no generated password, and mustChangePassword is true. Sign-in with the EXPLICIT password → 200. sys_account for the user = one row, provider "credential".
    A3 pass (pin) pins green at c419609: admin-identity-audit-trail.dogfood.test.ts 4✓, admin-credential-lifecycle.dogfood.test.ts 5✓, admin-route-nonadmin-refusal.dogfood.test.ts 7✓, admin-platform-admin-standing.dogfood.test.ts 8✓, security-plugin.test.ts 255✓, create-user-organization-join.dogfood.test.ts 3✓, admin-user-endpoints.t…
    A4 pass The target's live session read GET /data/showcase_task → 200. Admin revoke-user-sessions → 403 YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS. The same protected read → still 200, because the ruled refusal moved nothing.
    A5 pass (pin) pins green at c419609: admin-identity-audit-trail.dogfood.test.ts 4✓, admin-credential-lifecycle.dogfood.test.ts 5✓, admin-route-nonadmin-refusal.dogfood.test.ts 7✓, admin-platform-admin-standing.dogfood.test.ts 8✓, security-plugin.test.ts 255✓, create-user-organization-join.dogfood.test.ts 3✓, admin-user-endpoints.t…
    A6 pass POST /admin/impersonate-user {userId tgt} → 200. get-session with the impersonation credential → user tgt@c2.test, session.impersonatedBy = the admin's id. sys_session.impersonated_by, read by a second admin session, is the admin id. stop-impersonating → 200, and get-session with the returned credential → admin@object…
    A7 pass Setup → Users list, tgt row ⋯ menu: Edit / Ban User / Set Password / Impersonate User / Set Manager. Confirm dialog: "Start an impersonation session for this user? … actions will be logged." The action is POST /api/v1/actions/sys_user/impersonate_user 200. Screenshot: a red banner "You are impersonating Target — every…
    A8 pass (pin) pins green at c419609: admin-identity-audit-trail.dogfood.test.ts 4✓, admin-credential-lifecycle.dogfood.test.ts 5✓, admin-route-nonadmin-refusal.dogfood.test.ts 7✓, admin-platform-admin-standing.dogfood.test.ts 8✓, security-plugin.test.ts 255✓, create-user-organization-join.dogfood.test.ts 3✓, admin-user-endpoints.t…
    A9 pass Forger sb@c2.test (member): ban-user 403 PERMISSION_DENIED; list-users 403 YOU_ARE_NOT_ALLOWED_TO_LIST_USERS; create-user 403 PERMISSION_DENIED; set-role 403 …CHANGE_USERS_ROLE; remove-user 403 PERMISSION_DENIED; revoke-user-sessions 403 …REVOKE_USERS_SESSIONS. Also set-user-password 403 PERMISSION_DENIED and imperson…
    A10 pass Walled boot. Admin created org1 and org2 and set org1 active. create-user walled1@ with organizationId = org2 → 200, data.organizationId = org1's id, membershipCreated true. sys_member for the user = exactly one row, org1:member. (Also pinned at HTTP by J1.)
    A11 pass set-active {organizationId:null} → 200. create-user walled2@ → 400 NO_ACTIVE_ORGANIZATION "You are not working in an organization, so no user was created…". sys_user email=walled2@c2.test → 0 rows. (Also pinned by J2.)
    A12 pass On the same boot with invite-only resolved from env: A10's create answered membershipCreated true, and sys_member reads the one org1 member row. walled1 signs in (200).
    N0 pass No forged admin call succeeded (A9).
    N1 pass The banned sign-in was refused (A0).
    N2 pass Moot while revoke-user-sessions stays ruled. It was refused, and the protected read was re-driven (A4).
    N3 pass / PIN The explicit password won (A2). The cascade half is pinned (A5).

    identity-auth.session-list-revoke · rev 4 · PASS

    clause verdict evidence
    A0 pass A's GET /auth/list-sessions → 200, 5 rows, all A's: device-1, device-2, a browser session from an aborted first attempt, and two from user setup. GET /data/sys_session as A → 5 rows, revoked_at null on all, all user_id A. B's own read gave 3 row ids, and none appears in either of A's reads. A's data read filtered on B…
    A1 pass Screenshot of Account → Sessions / My Sessions · 5: 5 rows with User Agent (node ×3, Mozilla ×2), IP Address 127.0.0.1, Last Activity At, Active Organization, Created At and Expires At (+7 d), and a "Sign out other devices" button. The view's read (filter user_id = A) returned 5, the same as the API count.
    A2 pass First the item's trap: POST /auth/revoke-session {token: device-2's signed bearer} → 404 RESOURCE_NOT_FOUND "No session of yours matches the supplied token.", and device-2 still answers 200. Then with device-2's stored token from list-sessions → 200 {status:true}. device-2's protected GET /data/showcase_task → 401, …
    A3 pass Signed in a device-3 (200), then ran POST /auth/revoke-other-sessions from device-1 → 200. list-sessions → exactly 1 row, whose id equals device-1's get-session id. device-3 → 401, device-2 → 401, device-1 → 200, and B is untouched (200).
    A4 pass Admin Setup → Sessions all_sessions (screenshot): User column present, 8 live rows. The read is filtered revoked_at is_null, and the users cover A, B and the admin. revoked view (screenshot): User / IP Address / Revoked At / Revoke Reason / Created At, 5 rows for "sa", each revoke_reason user_revoked and revoked_a…
    A5 pass PATCH /data/sys_session/{id} → 405 OBJECT_API_METHOD_NOT_ALLOWED "API operation 'update' is not allowed…", as the owner and as the admin. DELETE → 405 ('delete') for both. POST create as admin → 405 ('create').
    N0 pass B's ids are absent from both of A's reads (A0).
    N1 pass device-1 survived the named revoke (A2).
    N2 pass The caller survived revoke-other-sessions, and every other session answers 401 (A3).
    N3 pass The revoked sessions were refused on the very next request (A2, A3).

    identity-auth.api-key-ui-lifecycle · rev 5 · PASS

    clause verdict evidence
    A0 pass Console form sign-in as kowner → Integrations page → typed "qa-c2-ui-key" → Generate key → confirm dialog "Create the API key "qa-c2-ui-key"? … It is shown ONCE." → Create key. POST /api/v1/keys 201 {data:{id:IVROUu0C-palhqEX,name,prefix:"osk_z9Xt8oaJ",key:"osk_z9Xt8oaJ…(full)",expires_at…}}. GET /data/sys_api_key/{…
    A1 pass GET /data/sys_session with x-api-key → 200, 5 rows, every user_id = kowner. Id set identical to kowner's session-cookie read (same 5 ids). Admin read of the same route → 200, 12 rows across 4 users.
    A2 pass Row action ⋯ → "Revoke API Key" → dialog "Revoke this API key? Any clients using it will immediately lose access." → confirm: POST /api/v1/actions/sys_api_key/revoke_api_key {recordId} 200. Next x-api-key read → 403 {"error":{"code":"API_KEY_REVOKED",…}}, no records key. Also driven by the item's literal door: PATCH…
    A3 pass Row action "Restore API Key" (dialog "…Existing clients holding the key will regain access.") → POST …/restore_api_key 200 → key read 200 with rows. PATCH {revoked:false} 200 → key 200.
    A4 pass kowner's mine view (network: filter user_id=kowner's id) lists IVROUu0C… with only the prefix. kother's mine view → 0 rows, and the prefix is absent from the page. Admin Setup → API Keys all_keys (no filter) lists the key with Owner "kowner". Also server-side: kother's unfiltered GET /data/sys_api_key lists only its…
    A5 pass Screenshot of the show-once dialog: "Your new API key / Copy this now — for your security it will not be shown again.", with the full osk_… secret, a Copy button, the "Send it as x-api-key…" note and Done. Screenshot of the re-opened My Keys list: 1 row, qa-c2-ui-key, prefix osk_z9Xt8oaJ, Default Organization, expiry …
    N0 pass No second read of the raw secret anywhere: list, get-by-id, select=key, the admin all_keys view or the page DOM (A0, A4).
    N1 pass The revoked key got 403 API_KEY_REVOKED on the very next request, through both doors (A2). An unknown key → 401 UNAUTHENTICATED, so the two refusals are distinct.
    N2 pass The key's row set equals the owner's and is smaller than the admin's 12 (A1).
    N3 pass kother's mine view and raw list exclude kowner's key (A4).

    identity-auth.self-service-profile-password · rev 3 · PASS

    clause verdict evidence
    A0 pass /_console/system/profile: set Name to "Prof Renamed C2" and clicked Save Changes. POST /api/v1/auth/update-user → 200 {status:true}. get-session.user.name = "Prof Renamed C2". After a page reload the field holds the same value, and the screenshot shows the card header "Prof Renamed C2".
    A1 pass setInputFiles on [data-testid=profile-avatar-file]. Network: POST /api/v1/storage/upload/presigned 200 (an uploadUrl under /api/v1/storage/_local/raw/…), PUT to that URL 200, POST /storage/upload/complete 200 {fileId 614ea2fb…, path user/614ea2fb….png, mimeType image/png, size 177}, then POST /auth/update-user 200. ge…
    A2 pass Clicked profile-avatar-remove-btn → POST /auth/update-user 200, and get-session.image is null. The screenshot shows the "PR" initials fallback and only an Upload button.
    A3 pass Through the card, with the correct current password and a new one: POST /auth/change-password 200. Sign-in with the new password → 200; with the old one → 401.
    A4 pass Through the card, with a WRONG current password: POST /auth/change-password → 400 {"code":"INVALID_PASSWORD"}. The next change, using the original password as current, succeeded, so the credential was unchanged by the refused attempt.
    A5 pass Before: an email+password sign-in for nopw@ → 401. Impersonated session on /system/profile: the card title is "Set Local Password" and there are 0 #current-password fields. Submit → POST /api/v1/auth/set-initial-password {"newPassword":…} → 200 {success:true}. Afterwards the email+password sign-in → 200.
    N0 pass The name survived the reload and get-session (A0).
    N1 pass The image is a storage URL backed by a committed file (A1).
    N2 pass A wrong current password got 400 INVALID_PASSWORD (A4).
    N3 pass The passwordless user got the set-initial form with no current-password field (A5).

    identity-auth.org-membership-team-management · rev 6 · PASS

    clause verdict evidence
    A0 pass Setup → the "Organization" nav link opened /_console/apps/com.objectstack.setup/sys_organization/record/org_mweu3tmljvvpslf6. That is the id that get-active-member and get-full-organization report (admin role owner), so the record page rendered, not the list. Screenshot: title "QA C2 Org Renamed", "Organization" badge…
    A1 pass update-member-role om1 → admin: 200, list-members reads admin. The gate is invite-member, re-signed-in each time: as member before → 403 YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION; as admin → 200; → delegated_admin 200 (reads delegated_admin); demoted to member 200, then invite → 403 again.
    A2 pass update-member-role with role guest / showcase_contributor / platform_admin → each 400 ROLE_NOT_FOUND, and the role still reads member. invite-member with the same three → each 400 ROLE_NOT_FOUND. sys_invitation count 1 → 1, and 0 rows have role guest.
    A3 pass POST /organization/update {data:{name:"QA C2 Org Renamed"}} → 200. get-full-organization name went "Default Organization" → "QA C2 Org Renamed". The record page title follows (screenshot), and the nav label stays "Organization".
    A4 pass om2 before removal: get-full-organization?organizationId=ORG → 200, set-active → 200. Admin remove-member → 200. om2's old session → 401 on get-session and on get-full, and its sys_session rows carry revoke_reason organization_membership_ended. After a fresh sign-in (200): get-full → 403 USER_IS_NOT_A_MEMBER_OF_THE_OR…
    A5 pass create-team "qa-c2-team" → 200. add-team-member for om1 and om2 → 200 each. GET /data/sys_team_member filtered on team_id reads exactly 2 rows (om1, om2). Adding om1 again → 200 and answers the SAME row id (MpkBhS7…), so no duplicate. Generic POST /data/sys_team_member → 405 OBJECT_API_METHOD_NOT_ALLOWED.
    A6 pass omf (member) forged five calls: update-member-role → 403 YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER; remove-member → 403 …DELETE_THIS_MEMBER; update → 403 …UPDATE_THIS_ORGANIZATION; create-team → 403 …CREATE_TEAMS_IN_THIS_ORGANIZATION; add-team-member → 403 …CREATE_A_NEW_TEAM_MEMBER. A snapshot of roster, roles, org na…
    N0 pass Every forge was refused server-side (A6).
    N1 pass Removal changed authorization (A4).
    N2 pass No out-of-vocabulary role was stored, and the role changes flipped the gate (A1, A2).
    N3 pass The nav resolved to the active org's record (A0).

    identity-auth.oauth-app-consent-loop · rev 3 · PASS

    clause verdict evidence
    A0 pass boot B: GET /.well-known/oauth-authorization-server 200, issuer http://localhost:41341/api/v1/auth, authorization_endpoint …/api/v1/auth/oauth2/authorize, token_endpoint …/oauth2/token, registration_endpoint …/oauth2/register; /auth/config features.oidcProvider true; authorize answered 200 → consent page. Boot C (OS_O…
    A1 pass admin POST /api/v1/auth/oauth2/create-client {redirect_uris:[https://app.example.test/callback], grant_types:[authorization_code,refresh_token]} → 201 with client_id + client_secret; GET oauth2/get-client?client_id=… → 200 public metadata, no client_secret key; GET /api/v1/data/sys_oauth_application (list and by id) c…
    A2 pass as ro1, GET oauth2/authorize (PKCE S256, scope openid profile offline_access) → /_console/oauth/consent; screenshot: "QA C3 App wants to access your account … (ro1@example.test)", "Access will be sent to: app.example.test", scopes "Confirm your identity · Read your basic profile (name, picture) · Stay signed in (refre…
    A3 pass Authorize → POST oauth2/consent 200 → redirect https://app.example.test/callback?code=…; POST oauth2/token (client_secret_basic, code_verifier) → 200 access_token + refresh_token + id_token, scope "openid profile offline_access"; introspect active:true; userinfo 200 sub=ro1; refresh_token grant 200 new pair. GET oauth…
    A4 pass repeat authorize, same client + scopes → 302 straight to https://app.example.test/callback?code=… (navigation trace has no /oauth/consent hop); that code exchanged 200
    A5 pass twice: authorize adding offline_access (from a recorded openid profile consent), and later adding email ("Read your email address" listed) → Deny → callback error=access_denied&error_description=User denied access, no code; get-consents unchanged (same id, scopes without the added one)
    A6 pass ro2 GET oauth2/get-consents → [] ; admin → [] ; ro1's list holds its consent. ro2 GET oauth2/get-consent?id=<ro1's> → 401, POST delete-consent → 401, ro1's consent still present. sys_oauth_consent over the data API → 404 OBJECT_API_DISABLED
    N0 pass secret not retrievable after registration (A1)
    N1 pass approve records consent (A3/A4); deny mints nothing (A5)
    N2 pass no cross-user consent visibility (A6)

    identity-auth.linked-accounts-social · rev 5 · PASS

    clause verdict evidence
    A0 pass W (member) signed in through the console form; POST /api/v1/auth/link-social {provider:qa-local, callbackURL:…/sys_account} → 200 {url: IdP authorize}; full-page navigation → 302 /api/v1/auth/callback/qa-local → landed /_console/apps/com.objectstack.account/sys_account; W's GET /api/v1/data/sys_account → total 2: cred…
    A1 pass W's Linked Accounts (Account app nav "Linked Accounts" → /sys_account/view/mine): screenshot shows "My Links · 2", rows credential + qa-local sub-w.local@example.test; U's qa-local row (sub-u.local@…) absent; W's API list also only W's rows
    A2 pass row menu "Unlink Account" → dialog "Unlink this identity link? The user will no longer be able to sign in with this provider…" → Confirm → POST /api/v1/actions/sys_account/unlink_account {recordId:, params:{accountId:}} → 200 {success:true,data:{status:true}}; reload shows "My Links · 1" (credential on…
    A3 pass (absence recorded) boot B (no provider): Linked Accounts mine view shows "My Links · 1" (credential); view overflow menu = "Add to favorites" only, row menu = "Unlink Account" only; no link affordance anywhere. Per the clause this absence does not discriminate (no link action exists on any boot); no dead-ending link affordance seen
    A4 pass as W and as the platform admin: POST /api/v1/data/sys_account → 405 OBJECT_API_METHOD_NOT_ALLOWED allowed ["get","list","aggregate","export"]; DELETE /data/sys_account/ → 405; PATCH → 405 (admin)
    N0 pass each UI-visible link has its sys_account row (A0)
    N1 pass the unlinked row is gone server-side (admin read), not just hidden (A2)
    N2 pass W never saw U's link (A1); cross-user POST /auth/unlink-account with U's row id from W → 400 ACCOUNT_NOT_FOUND, U's row intact
    N3 pass no link affordance on the provider-less boot (A3)

    identity-auth.identity-import-wizard · rev 3 · PASS

    clause verdict evidence
    A0 pass (invite half) wizard POST /api/v1/auth/admin/import-users {mode:insert, passwordPolicy:auto} → 200 summary.delivery {emailInvite:2, smsInvite:0, temporary:0}, rows[].delivery "email", action created; sink captured 2 mails "Dev Admin invited you to ObjectStack" with a set-your-password link /api/v1/auth/reset-password/?callba…
    A1 pass wizard {passwordPolicy:temporary} 3 rows → 200 delivery {temporary:3, emailInvite:0}; every row carries temporaryPassword; no new mail at the sink; result step text "Temporary passwords — shown once, never stored… imp.tmp1@example.test: …" lists all 3 with "Download CSV (3)"
    A2 pass auto-invite row: GET the mailed link → 302 /_console/reset-password?token=…; POST /auth/reset-password {token,newPassword} 200 {status:true}; POST /auth/sign-in/email imp.auto1 → 200 token. temporary rows: sign-in/email imp.tmp1 and imp.tmp3 with the revealed passwords → 200 token each
    A3 pass wizard re-run of the same CSV with "When a row matches an existing record: Update if matched, else create", Match on Email → POST {mode:upsert, matchBy:email, passwordPolicy:temporary} → 200 summary created 0 / updated 3, result text "3 updated"; sys_user count for imp.% stays 5 (one row per email)
    A4 pass read-only scan of every text column of all 116 tables in A.db (+ WAL) and the telemetry db for the three revealed passwords: 0 hits; 0 hits in the boot logs; upsert rows carry no temporaryPassword
    A5 pass plain member POST admin/import-users → 403 PERMISSION_DENIED "Admin role required"; organization admin → same 403; anonymous → 401 UNAUTHENTICATED; sys_user rows for forged%@ = 0
    N0 pass every temporary row carried a one-time credential that authenticates (A1/A2)
    N1 pass no plaintext one-time password persisted (A4)
    N2 pass upsert updated, did not duplicate (A3)
    N3 pass non-admin and org-admin refused (A5)

    identity-auth.first-run-owner-bootstrap · rev 5 · PASS

    clause verdict evidence
    A0 pass Anonymous GET /api/v1/auth/bootstrap-status before the wizard → 200 {"hasOwner":false}. After the wizard → 200 {"hasOwner":true}. /auth/config on this boot: emailPassword.disableSignUp true, audiencePosture invite_only.
    A1 pass The wizard's POST /api/v1/auth/sign-up/email {email owner@c2.test, name "Owner C2"} → 200 {token, user owner@c2.test} on the OS_DISABLE_SIGNUP boot with zero users. get-session → owner@c2.test with positions [platform_admin, org_owner, everyone]. sys_user afterwards = [owner@c2.test].
    A2 pass Typed org name "大阪本社✨" (CJK + emoji only). The wizard sent POST /organization/update {organizationId org_mweujfd6…, data:{name:"大阪本社✨"}} → 200 {name "大阪本社✨", slug "default"}, then set-active. There was no organization/create in the trace. GET /organization/list = exactly one org, {name "大阪本社✨", slug default}.
    A3 pass Main-frame document requests: /_console/setup, then after submit two document loads of /_console/ (10:41:25.517 and .525), landing on /_console/home. That is a full-document exit. Landing screenshot: "Good morning, Owner C2.", the first-run checklist card, and "Your apps 1" with the Showcase tile. The authed /api/v1/m…
    A4 pass Door 1, this boot, owner present: uninvited forger@c2.test → 403 SELF_REGISTRATION_CLOSED (the audience gate, recorded not scored). The owner invited invited@c2.test (200, pending), and the forge presenting its invitationId → 400 EMAIL_PASSWORD_SIGN_UP_DISABLED. That is the toggle, so door 1 is proven. Door 2, stock c…
    A5 pass Anonymous /_console/setup after bootstrap → /_console/login?redirect=%2Fsetup. The screenshot shows the sign-in card, with no owner or create button. The owner, signed in through the form, opens /_console/setup → /_console/apps/com.objectstack.setup/dashboard/system_overview; the screenshot shows the Setup System Over…
    N0 pass No wizard once hasOwner is true (A5). bootstrap-status was read fresh first.
    N1 pass No post-bootstrap self-minted account on either boot (A4).
    N2 pass The exit was a document navigation into the owner's console (A3).
    N3 pass The CJK + emoji name applied, and the slug stayed "default" (A2).
    N4 pass Exactly one organization, with no create call (A2).
  2. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    qa#3 · os-tesla · session_01L9qibw6yXcLi9aRRBpPT4W

    Per-clause tables (2)

    identity-auth.self-signup-gate · rev 8 · PASS

    clause verdict evidence
    A0 pass stock /auth/config: emailPassword.disableSignUp false and features.audiencePosture "invite_only". disabled: disableSignUp true and audiencePosture "invite_only". These are independent axes.
    A1 pass stock: uninvited POST /auth/sign-up/email forger@c2.test → 403 {"code":"SELF_REGISTRATION_CLOSED","message":"Self-registration is closed (audience posture invite_only). Ask an administrator for an invitation."}. Admin sys_user filter email=forger@c2.test → 0 rows; user count 4 → 4.
    A2 pass stock: admin invite-member invitee@c2.test → 200, id T4llhQ…, status pending. The same email without invitationId → 403 SELF_REGISTRATION_CLOSED. With invitationId → 200 {token, user invitee@c2.test}, and get-session with that token → invitee@c2.test.
    A3 pass disabled boot: /_console/register?redirect=%2Fapps%2Fshowcase_app landed on /_console/login?redirect=%2Fapps%2Fshowcase_app, so the redirect was kept. The screenshot shows the sign-in card. Its links are Forgot password, Privacy, Terms and Powered by, with no register link.
    A4 pass disabled boot: INVITED invited@c2.test presenting its invitationId → 400 {"code":"EMAIL_PASSWORD_SIGN_UP_DISABLED"}, which is the toggle. UNINVITED forger@c2.test → 403 SELF_REGISTRATION_CLOSED, the audience gate. sys_user before and after = [owner@c2.test].
    N0 pass No synthetic 200: every uninvited attempt (5 across the two boots) answered 403 with a code.
    N1 pass No row for any refused address.
    N2 pass The endpoint was refused server-side beside the UI bounce (A3, A4).
    N3 pass The invited registrant was admitted on invite_only (A2).
    N4 pass stock login (screenshot): the links are Forgot password / Privacy / Terms / Powered by, with no sign-up. /_console/register renders data-testid register-by-invitation ("Self-registration is not open on this system. Ask an administrator for an invitation.") with 0 inputs. /_console/login?redirect=/accept-invitation/ID …

    identity-auth.email-verification-loop · rev 6 · PASS

    clause verdict evidence
    A0 pass ev1 registered through /_console/register (POST sign-up/email 200 token:null, emailVerified false) → routed to /_console/verify-email-prompt?email=ev1%40example.test; POST /api/v1/auth/sign-in/email ev1 → 403 {"message":"Email not verified","code":"EMAIL_NOT_VERIFIED"}; get-session with that cookie jar → 401; console …
    A1 pass sink MSG "Verify your ObjectStack email address" to ev1 at sign-up; admin GET /api/v1/data/sys_identity_email row e872fbec… status sent, related_object sys_user, body_text "Please verify your email (ev1@example.test) by opening this link: http://localhost:41343/api/v1/auth/verify-email?token=eyJ…"
    A2 pass ev2 (API sign-up, own sys_identity_email row 90cc7d87…): its sign-up token opened in the console /_console/verify-email?token=… → page "Email verified · Your email is confirmed. You can now sign in. · Go to sign in"; the page's GET /api/v1/auth/verify-email?token=… → 200 {"status":true,"user":null}; sign-in/email ev2 …
    A3 pass console "Resend verification email" → "Verification email sent! … Email sent! Check your inbox"; second, newer row 1edd5717… with its own token; GET verify-email with the RESEND token while ev1 still unverified → 200 {status:true,user:null}; sign-in ev1 → 200, get-session emailVerified true. Recorded (not scored): the…
    A4 pass /_console/verify-email?token=garbage.not.a.token → page "Verification failed · Invalid token · Back to sign in"; GET verify-email garbage → 401 {"code":"INVALID_TOKEN"}; ev2 sign-in afterwards still 403 EMAIL_NOT_VERIFIED
    A5 pass boot B: /auth/config emailPassword.requireEmailVerification false; invited registrant ro1 signed up (emailVerified false) and POST sign-in/email → 200 token at once, no challenge. Boot D (open, no opt-out): /auth/config emailPassword {enabled:true, disableSignUp:false, requireEmailVerification:true}, audiencePosture o…
    N0 pass server refuses the unverified sign-in (A0)
    N1 pass every verification send landed at the sink with a row (2 for ev1, 1 for ev2)
    N2 pass forced boot advertises true (A5)
    N3 pass stock boot does not challenge (A5)
    N4 pass garbage token → failure state, account unchanged (A4); see probes

    identity-auth.workspace-org-switch · rev 6 · PASS

    clause verdict evidence
    A0 pass single (/auth/config tenancyPosture single, multiOrgEnabled false): admin and member, Home and task list: workspace-switcher 0, current-organization-indicator 0; top-bar screenshot shows logo / Showcase app pill / PREVIEW, no org name. isolated and group with ONE membership: indicator present ("Current organization Al…
    A1 pass the menu click sent POST /api/v1/auth/organization/set-active {organizationId: Beta} → 200 {name:"Beta iso", id…} (group: "Beta grp"); post-reload GET get-active-member → 200 organizationId = Beta, role owner (both postures)
    A2 pass after the dropdown click, the main frame made a navigation request GET /_console/ (document), a window.__qaMarker set before the click was gone afterwards, final URL /_console/home (landing resolution); both postures
    A3 pass isolated: sentinel PwiZl… GET 200 before the switch → 404 RECORD_NOT_FOUND after, title-filtered list total 0, every readable task row carries Beta's organization_id (10). group: the old-org sentinel VswvB… still 200 (reads span orgs; rows by org Alpha 11 / Beta 10), a task created after the switch → 201 organization_…
    A4 pass group dropdown: header "Working organization", hint "New records are created here. Views show data from all your organizations." (workspace-switcher-group-hint 1), Alpha ✓, Beta grp, Manage members, Create organization. isolated dropdown (screenshot): "Switch organization", Beta iso, Alpha ✓, Manage members, Create or…
    A5 pass as the member, forged POST set-active {organizationId: the admin's Default Organization (no membership)} → 403 {"message":"User is not a member of the organization","code":"USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION"} on both postures; get-active-member → 400 NO_ACTIVE_ORGANIZATION (cleared, the expected "none"); reload…
    N0 pass no org chrome under single (A0)
    N1 pass isolated reads follow the switch; meta unchanged (A3)
    N2 pass no one-entry dropdown at one membership; the indicator has no click target (A0)
    N3 pass forged set-active refused (A5)
    N4 pass the switch is a full document load (A2)

    identity-auth.implicit-account-linking-ownership · rev 5 · PASS

    clause verdict evidence
    A0 pass U invited+signed up (get-session emailVerified:false). POST /auth/sign-in/social {provider:qa-local} 200 → IdP → 302 /api/v1/auth/callback/qa-local → final URL /_console/login?qa=err&error=account_not_linked&error_description=An account with this email already exists and its email address is not verified…; get-sessi…
    A1 pass U verified through the product: send-verification-email 200 → SMTP sink captured "Verify your ObjectStack email address", sys_identity_email row status sent with tokened link → GET verify-email 200 {status:true,user:null} → email_verified true. Fresh-context implicit sign-in → final URL `/_console/apps/com.example.sho…
    A2 pass as U POST /auth/unlink-account {accountId:x9GaX2…} 200 {status:true}; sys_account(U) = credential only. Fresh-context implicit sign-in → error=account_not_linked&error_description=This sign-in method was unlinked from the account. Sign in another way and link it again…; get-session 401; sys_account(U) still credenti…
    A3 pass U signed in, POST /auth/link-social 200 {url} → full-page round-trip → landed /_console/apps/com.objectstack.account/sys_account; mine view lists credential + qa-local (new row BSTmY…); next fresh-context implicit sign-in lands ?qa=ok, get-session U. W (unverified, invited sign-up) explicit link-social → W's own row…
    A4 pass (pin) pins green at c419609: implicit-account-linking.test.ts 23✓
    N0 pass no sys_account row and no session after the unverified implicit sign-in (A0)
    N1 pass U's email_verified stayed false after the refused implicit link (A0)
    N2 pass post-unlink implicit sign-in did not re-create the link (A2)
    N3 pass explicit links admitted for verified U and unverified W (A3)
    N4 pass (pin) pins green at c419609: implicit-account-linking.test.ts 23✓
    N5 pass fixture IdP answered email_verified:true on every scored leg; an email_verified:false leg was run only as a probe
    N6 pass fresh context per implicit sign-in, pre-session 401 recorded each time

    identity-auth.sso-provider-groups-claim · rev 1 · PASS

    clause verdict evidence
    A0 pass POST admin/sso/get-groups-claim {providerId:qa-sso} → 200 {"providerId":"qa-sso","groupsClaim":null} (exactly two keys); {providerId:nope} → 404 RESOURCE_NOT_FOUND SSO provider "nope" was not found.
    A1 pass set {groupsClaim:"groups"} → 200 {providerId, groupsClaim:"groups"}; get → "groups"; stored oidc_config (read-only DB read) diff before→after-set = only mapping.extraFields.groups added; set {groupsClaim:""} → 200 groupsClaim null; get → null; stored oidc_config before vs after-clear: 0 differing members (issuer, cl…
    A2 pass organization administrator (invited role admin, accepted, sys_member.role=admin): get-groups-claim → 403 {"code":"PERMISSION_DENIED","message":"Admin role required"}; set-groups-claim {groupsClaim:"pwned"} → 403 same body; admin read-back unchanged (null)
    A3 pass Setup → SSO Providers → qa-sso record drawer; its overflow menu lists Edit Sign-In Options · Edit SAML Settings · Edit OIDC Settings · Show Groups Claim · Edit Groups Claim · Show Attribute Mapping · Edit Attribute Mapping; Show Groups Claim (Provider ID prefilled qa-sso) → POST /api/v1/actions/sys_sso_provider/show…
    N0 pass no get answer carried a secret or another mapping member (A0/A1 bodies)
    N1 pass 0 stored-config diffs after set+clear; an extra body key mapping:{email:"x"} on set was ignored (email mapping still "email")

    integration-system.flow-connector-picker · rev 5 · PASS

    clause verdict evidence
    A0 pass ground truth: GET /api/v1/automation/connectors → 200, total 5: showcase_status_api, showcase_status_openapi, showcase_mcp_tools (origin declarative), rest, slack (origin plugin). Screenshot after selecting the Call node and focusing the Connector field: inspector "NODE · CONNECTOR Call", the focused Connector* field …
    A1 pass option labels (DOM): "Status API (Declarative REST Instance) (showcase_status_api) · declarative", "Status API (Declarative OpenAPI Instance, File-Path Spec) (showcase_status_openapi) · declarative", "Dev Tools (Declarative MCP Instance) (showcase_mcp_tools) · declarative". The plugin ones, "REST Connector (rest)" and…
    A2 pass pick showcase_mcp_tools → Action datalist = ["echo_upper => Echo Upper (echo_upper) — Upper-case the input text (deterministic showcase demo tool)."], the descriptor's only action. Pick echo_upper → screenshot: a typed "Text" input with hint "Text to upper-case", "Actions of showcase_mcp_tools.", Input "No entries yet…
    N0 pass no picker entry is absent from the API and none is missing (A0 diff empty)

    records-forms.form-dirty-guard · rev 7 · PASS

    clause verdict evidence
    A0 pass DIRTY (title typed, not saved): X (Close) → alertdialog "Discard changes? · You have unsaved changes. If you close this form now, your edits will be lost. · Keep editing · Discard", form stays open. Escape → the same prompt. Backdrop click on the overlay → the same prompt (screenshot: blurred form behind the "Discard …
    A1 pass discard: a full-record hash before = after every discard (0b745e4661, title "Sales Prospect"); 0 PATCH/PUT/POST on /data/showcase_contact from any close path. Stay: "Keep editing" → the form is still open and holds "C1 dirty X". After a Discard, reopening Edit on the same page shows the server value, so the form state…
    A2 pass (pin) pins green at c419609: discardGuard.test.tsx 14✓
    N0 pass no dirty edit was lost without a prompt on X / Escape / backdrop; a pristine form never prompted on any path

    studio-authoring.flow-status-matches-runtime · rev 5 · FAIL

    clause verdict evidence
    A0 pass pairing (rail sr-text and row title / header "Status:" pill vs _status {enabled,bound}). DEFAULT boot: c1st_on_create while Off: 'Switched off' / 'Switched off' vs {false,false}; after enable+publish 'Switched on' ('Enabled · bound to its trigger') vs {true,true,record_change}; after disable+publish 'Switched off' vs …
    A1 fail (stale spec) schedule flow on the default boot. Problems tab (screenshot): the tab label is "Problems" with no count; a note "Not running here — This deployment keeps scheduled work that comes with a package switched off. Whoever operates this deployment can switch it on."; and "The structure checks found nothing to fix. The note …
    A2 pass header switch on c1st_on_create (aria-checked false→true) → toast 'Switched on — publish to apply', PUT /meta/flow/c1st_on_create?mode=draft&package=com.qa.c1st&surface=studio 200. Publish → dialog "Pending changes … 1 item · 1 flow … C1 On Create … ~ Status" → POST /packages/com.qa.c1st/publish-drafts 200, toast 'Pub…
    A3 pass scheduled boot: c1st_sched rail 'C1 Minute Digest · Scheduled · Switched on', header 'Status: Switched on / Enabled · bound to its trigger', _status {enabled:true, bound:true, triggerType:'schedule'}. Authored 10:50:47Z; GET /automation/c1st_sched/runs → run_afd9987f completed, trigger {type:'schedule'}, started 10:51…
    N0 pass no green status dot or "Switched on" status word for an unbound flow: c1st_sched has a grey dot, data-status "not-running", 'Not running here', with _status bound:false. The default-boot schedule flow is a note with zero problems, not an error. See note 2 on the header's separate enable switch
  3. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    qa#3 · os-tesla · session_01L9qibw6yXcLi9aRRBpPT4W

    Close-out 2026-11-13T10:59Z.

    Extracted: #33060 · approvals.record-page-decisions · acceptance[4]
    Extracted: #33054 · studio-authoring.flow-status-matches-runtime · acceptance[1]
    Parked-on: #32908 · checklist-accuracy notes and fixture gaps of this slice

  4. objectstack-fleet commented on Nov 13, 2026

    @objectstack-fleet
    ContributorAuthor

    qa#3 · os-tesla · session_01L9qibw6yXcLi9aRRBpPT4W

    Closed 2026-11-13T11:00Z: the c419609 browser delta is judged, two stale specs on existing cards; close-out above.

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