Skip to content

Checklist api backend

github-actions[bot] edited this page Nov 12, 2026 · 1 revision

api-backend

Generated by pnpm gen:checklist-status from docs/qa/platform-checklist/areas/*.json. ⛔ Do not edit this page — edits go to the area JSON and this page follows on the next run. This page is Checklist-api-backend.

REST / batch / formula / build-time gates

33 active · 0 planned

Active (33)

id title priority status personas
api-backend.batch-transactional-discovery transactionalBatch capability bit matches the mounted /batch behavior — atomicity, rollback codes, size cap P1 active admin (or any member entitled to the objects the batch touches)
api-backend.formula-gates Formula runtime fixes hold and date-arithmetic fails at build time P1 active —
api-backend.enforce-or-remove-authoring-gates Removed/retired authoring keys fail at parse/build with located guidance P1 active —
api-backend.server-timing-admin-gated Server-Timing: the per-request X-OS-Debug-Timing path discloses spans to admins only, OS_SERVER_TIMING global mode to everyone, per-query detail admin-only always — on the server os dev actually runs P2 active admin, non-admin member
api-backend.query-contract-matrix Data-API query contract: every filter operator gives known answers; $-params, select, sort, expand honored; malformed input 400s with the exact code P0 active admin (baseline known-answer runs), contributor (the RLS/FLS-restricted persona for the expand enforcement clause)
api-backend.error-envelope-ledger Sampled endpoints return the standard error envelope with ledgered codes — no invented codes, no retired keys, statuses match the map P1 active admin, a baseline member (for the 403 sample), anonymous (for the 401 sample)
api-backend.bulk-write-contract Bulk write doors (createMany/updateMany/deleteMany, per-object batch): per-row outcomes, size cap, single/bulk parity, no silent full-table writes P1 active a baseline member (bulk writes on showcase_private_note — also exercises the owner-scoping of #2982), admin (cap and parity runs)
api-backend.route-ledger-live-parity Every route family across all ELEVEN route ledgers — REST, dispatcher, auth, storage/i18n/settings/datasource services, metadata HMR, trigger-api, cloud-connection, console static — is actually mounted on the live server — no route that exists only in unit tests P2 active admin (authenticated, so a 401 cannot mask a 404)
api-backend.declarative-endpoint-execution Metadata-authored api endpoints mount as real URLs with their declared auth/cache policy P1 active admin, anonymous
api-backend.package-rest-lifecycle Package REST lifecycle: POST /packages creates (201), a duplicate name is refused 409 (no silent manifest clobber), PATCH partial-patches the manifest, and an explicit overwrite re-install replaces in place without duplicating P2 active admin (manage_metadata / package authoring)
api-backend.api-console-discovery-execute Developer API Console (a deep link on a stock boot): the endpoint tree mirrors /discovery, a seeded-object GET executes with live JSON + status + timing, and a malformed body surfaces the server error envelope verbatim P2 active admin
api-backend.filter-comparand-conformance Filter comparand conformance: only the accepted comparand TYPES pass the where-door, a bigint past the exact-integer limit is refused, and a dotted head is classified rather than guessed P1 active seeded admin
api-backend.date-range-preset-matrix Every date-range preset resolves to its declared macro window, and a bare preset name used as a raw comparand is refused with the prescription P2 active seeded admin
api-backend.retired-def-refusal A retired metadata DEFINITION (whole schema type withdrawn) refuses at every authoring door — distinct from a retired key, and the door that must not fall through to 'unknown type, ignored' P1 active seeded admin, build-time author (no session)
api-backend.packaged-action-disabled-dispatch A disabled packaged action is refused 409 ACTION_DISABLED at BOTH dispatch doors — after the capability gate (a 403 is never an oracle for the switch), before the param contract and the record load, durable across restart P0 active admin (dev admin — holds manage_metadata for the activation flips via admin_full_access, and is the invoker on the 409 legs; the probe actions declare no requiredPermissions, so any authed caller clears the ADR-0066 D4 gate), any authenticated caller for the 403-oracle leg — showcase_zoo_perm_missing requires showcase.restricted_ops, a capability the app declares and grants to NOBODY (predicate-matrix.action.ts), so even the admin answers 403 there
api-backend.action-activation-door-contract POST /actions/_activation/:object/:action — the activation door's ordered arms: authority before body and lookup, the exact 400 shapes, 404 undeclared, 409 ambiguity, 501/503 honesty, and the reserved segment surviving the real mount P2 active admin (dev admin — manage_metadata via admin_full_access; the happy-path and body-arm caller), a non-admin member for the authority-first leg (Mei Phone phone.demo@example.com or Ada Auditor auditor.demo@example.com, DEMO_PERSONA_PASSWORD 'showcase123' — no showcase permission set grants manage_metadata, only admin_full_access carries it), anonymous (the 401 baseline control)
api-backend.aggregate-contract-matrix Server-side aggregation contract: every AggregationFunction × DateGranularity gives known answers — ruled empty-set values, ISO-Monday week buckets, retired array_agg/string_agg refuse with prescriptions, and aggregate stays secured (engine-only, never raw driver) P1 active admin (known-answer runs over the seeded baseline), a restricted non-admin persona for the secured-aggregate leg whose visible row set provably DIFFERS from the admin's: the search:qa-contributor-bound-member persona (a contributor-bound member who reads only its own showcase_invoice rows — give it one of its own, on an ACTIVE account). ⛔ Not the seeded demo personas: Mei Phone is refused showcase_invoice at the object gate (403 PERMISSION_DENIED, no answer to compare) and Ada Auditor holds viewAllRecords (her counts equal the admin's), so neither can tell a secured aggregate from a raw one
api-backend.formula-stdlib-matrix CEL formula stdlib: all 30 bare-callable stdlib functions give known answers against the BUILT package — calendar-day timezone contract, addMonths month-end clamp, floor(-1.2)==-2, null-dropping joins, mixed int/double arithmetic, isoDate/isoDatetime text — and a formula calling an unregistered function is REFUSED at the authoring door (422), never stored to read null P1 active build-time harness (no session — the matrix runs against the built @objectstack/formula package), admin (the live read-path parity leg)
api-backend.api-methods-verb-gate enable.apiMethods three-state whitelist on the REST boundary: 404 hides apiEnabled:false, 405 OBJECT_API_METHOD_NOT_ALLOWED carries the EFFECTIVE derived set, bulk∧child + writeMode-precise import, legacy 8-value strip-and-WARN (deny-all cliff), the object gate answers ahead of the caller's 403, batch gated per-op before the txn P1 active admin (proves the gate is OBJECT-level: even admin_full_access answers 405/404 on a narrowed object), a non-admin member for the ordering leg (Mei Phone phone.demo@example.com / Ada Auditor auditor.demo@example.com, DEMO_PERSONA_PASSWORD 'showcase123'), anonymous (the 401 baseline control)
api-backend.rest-crud-config-contract RestServerConfig.crud is the CRUD surface's construction contract: five switches gate six mounts, dataPrefix moves the routes and their discovery advertisement together, and both tombstones refuse at construction P2 active admin, authenticated — on the live-boot half a 401 must never be mistaken for an absent mount, no persona for the variant half: a non-default crud block is authorable only by a programmatic embedder (knownGaps), so those clauses are driven in a scratch harness, not by a signed-in user
api-backend.rest-batch-config-contract RestServerConfig.batch is the bulk surface's construction contract: three switches and enableBatchEndpoint gate four per-object mounts (each ANDed with its protocol member), maxBatchSize is the one cap every bulk door measures, and both tombstones refuse at construction P2 active admin, authenticated — the live-boot half, no persona for the variant half: a non-default batch block is authorable only by a programmatic embedder (knownGaps)
api-backend.rest-metadata-config-contract RestServerConfig.metadata is the metadata surface's construction contract: prefix moves every metadata mount and their discovery advertisement, four endpoint switches each gate exactly the face its name states (#15542/#15854), maskObjectFields is the ADR-0106 D8 disclosure gate, and both tombstones refuse at construction P1 active admin, authenticated — the live-boot half, a restricted member who cannot read every field of showcase_project — a member bound to the showcase_client_liaison position (showcase_client_liaison set: budget, budget_remaining and spent unreadable) — the masking clause is only observable as a non-admin, no persona for the variant half: a non-default metadata block is authorable only by a programmatic embedder (knownGaps)
api-backend.rest-route-generation-tombstones RestServerConfig.routes is an all-tombstone sub-object: every one of the four retired selectors refuses at construction with the prescription that names where the contract moved, while the empty block still constructs P2 active no persona — this is a construction-time and build-time contract, driven by an embedder authoring a config, not by a signed-in caller
api-backend.second-object-exposure-and-internal-fields Every read that reaches a NON-addressed object asks that object's own enable block (nested-relation filter → 404/405, $expand withheld to the bare id, search skips it), and an internal: true field is refused 400 INVALID_FIELD in every evaluate position (filter, sort, groupBy, aggregate, $expand where/orderBy) P1 active seeded admin (admin@objectos.ai / admin123) — the Default Organization owner and platform admin on a fresh boot, org member — created by the admin with POST /api/v1/auth/admin/create-user (stock showcase self-registration is closed: audience posture invite_only answers sign-up 403 SELF_REGISTRATION_CLOSED); joins the Default Organization as member, not platform admin, and lands mustChangePassword:true, i.e. a HELD session until it changes its password (POST /api/v1/auth/change-password)
api-backend.data-request-shape-and-query-arity Request-shape gates on v18: a /data write with a null / array / scalar body or an empty version token is refused 400 (never an unguarded 200), a repeated single-valued query name is refused 400 on the dispatcher doors (a doubled keepData no longer drops tables), and a stale If-Match on an unreadable row answers 404 P1 active seeded admin (admin@objectos.ai / admin123) — the Default Organization owner and platform admin on a fresh boot, org member — created by the admin with POST /api/v1/auth/admin/create-user (stock showcase self-registration is closed: audience posture invite_only answers sign-up 403 SELF_REGISTRATION_CLOSED); joins the Default Organization as member, not platform admin, and lands mustChangePassword:true, i.e. a HELD session until it changes its password (POST /api/v1/auth/change-password)
api-backend.meta-read-doors-v18 /meta read doors on v18: GET /meta/app/PACKAGE_ID returns that package's app, ?member= selects one bundle member on by-name doors, an unknown query parameter on any by-name door is refused 400, ?package=all folds to the no-parameter answer, and the object definition marks an FLS-denied field unsortable for that caller P2 active seeded admin (admin@objectos.ai / admin123) — the Default Organization owner and platform admin on a fresh boot, org member — created by the admin with POST /api/v1/auth/admin/create-user (stock showcase self-registration is closed: audience posture invite_only answers sign-up 403 SELF_REGISTRATION_CLOSED); joins the Default Organization as member, not platform admin, and lands mustChangePassword:true, i.e. a HELD session until it changes its password (POST /api/v1/auth/change-password)
api-backend.error-sentences-and-unique-violation-wire-code Error answers made honest on v18: a hook refusal on a hand-built write route answers 400 with the hook's own sentence, a crash answers a generic 500 that withholds the caught message, and a unique-constraint refusal is 409 UNIQUE_VIOLATION with details at the action door, MCP and sandboxed bodies too P2 active seeded admin (admin@objectos.ai / admin123) — the Default Organization owner and platform admin on a fresh boot
api-backend.actions-namespace-address An action call names its holder: POST /api/v1/actions/:ns/:object/:action[/:recordId], /actions/:ns/global/:action and the _env forms run that namespace's (or the environment's) declaration, an action another package binds to an object is reachable by its namespace, and the bare call is unchanged P1 active admin (the seeded admin): every call
api-backend.meta-qualified-item-read A metadata item reads by its qualified address: GET /api/v1/meta/:type/:ns/:name and /meta/:type/_env/:name name the holder exactly, a bare read of a name two packages hold answers 409 naming both, and ?package= stays the exact qualifier P1 active admin (the seeded admin): every read, a caller the bare item read refuses (no session): the parity leg
api-backend.automation-namespace-address The automation, legacy trigger, connector connection and inbound hook doors take the namespace and _env segments: /automation/:ns/:name[/trigger|/toggle|/clone|/runs…], /automation/trigger/:ns/:name, /automation/connectors/:ns/:name/connection and /automation/hooks/:ns/:flowName/:hookId serve the name's one holder exactly as the bare path does, and refuse any other qualifier with 404, 409 RESOURCE_CONFLICT or, for the hook, the door's own 401 P1 active admin (the seeded admin): every read, run and connection call, an external sender with no session: the inbound hook posts, a member without manage_metadata (check GET /api/v1/auth/me/permissions first): the gate leg
api-backend.client-sdk-namespace-address The client SDK names an item's package: automation.*, actions.invoke / invokeGlobal and meta.getItem take an optional namespace, send the qualified path the server reads, send the bare path unchanged without one, refuse before sending any path the router would read as another request, and hand a 409's candidates to the caller P2 active an integrator calling the platform through @objectstack/client, signed in as the seeded admin (admin@objectos.ai / admin123) with client.auth.login: every call
api-backend.recycle-bin-delete-entry-restore A business-record delete keeps its rows in the recycle bin: the delete answer names the entry, GET /api/v1/data/:object/recycle-bin lists it with its cascade and retention, and POST …/recycle-bin/:entryId/restore writes every row back under its original id P1 active seeded admin (admin@objectos.ai / admin123): every call
api-backend.recycle-bin-refusals-purge-empty Recycle-bin refusals and removals: a detail whose master is in the bin or purged answers 409 naming why, purge and empty remove entries for good, the listing's paging is declared, and a non-manager or anonymous caller is refused P1 active seeded admin (admin@objectos.ai / admin123): every positive call, a contributor-bound member (the search:qa-contributor-bound-member recipe): the non-manager probes, an anonymous caller (no session): the 401 probe

Clone this wiki locally