Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .changeset/owner-id-anchor-and-bulk-write-scoping.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,6 @@ row-level scoping keys off to decide who may update/delete a record.
now correctly affects zero rows instead of all of them.

Proven end-to-end on the real showcase app
(`packages/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts`) and pinned
(`packages/qa/dogfood/test/owner-anchor-and-bulk-writes.dogfood.test.ts`) and pinned
in the ADR-0096 authz-conformance ledger (`ownership-anchor-guard`,
`bulk-write-owner-scoping`).
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -107,18 +107,26 @@ jobs:
# spec sits at the root of the dependency graph, so spec-touching PRs
# still run (close to) everything — but the many PRs that don't touch
# spec skip the bulk of the 75-package matrix.
# --concurrency=4: turbo's default (10) oversubscribes the 4-vCPU
# hosted runner. As the task graph grew past ~100 tasks (connector
# packages + qa gates), parallel vitest workers + tsup DTS builds
# started exhausting runner memory: plugin-audit#test died with ZERO
# output (kernel OOM-kill signature) deterministically on the runner
# while passing everywhere else — first seen on main @4f8c2d1,
# reproduced twice on #3037. Matching concurrency to the core count
# bounds peak memory; the job is CPU-bound anyway.
- name: Run affected tests (PR)
if: github.event_name == 'pull_request'
env:
TURBO_SCM_BASE: ${{ github.event.pull_request.base.sha }}
run: pnpm turbo run test --affected
run: pnpm turbo run test --affected --concurrency=4

# Push to main: full run, but exclude spec's plain test task — the
# coverage step below executes the exact same 250-file spec suite once,
# with coverage. Previously the spec suite ran twice per push.
- name: Run all tests (push)
if: github.event_name == 'push'
run: pnpm turbo run test --filter=!@objectstack/spec
run: pnpm turbo run test --filter=!@objectstack/spec --concurrency=4

- name: Generate coverage report
if: github.event_name == 'push'
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,15 @@ jobs:
# bad signal to copy from. tsup transpiles them without a full typecheck,
# so build alone will not catch type drift — typecheck them explicitly.
# They import from built workspace packages, so the packages must be built
# first for cross-package type resolution to succeed.
# first for cross-package type resolution to succeed. The examples'
# dependency closure is requested EXPLICITLY (`./examples/*^...` = deps
# of the examples, not the examples themselves): the bare `./packages/*`
# glob only matches direct children, and the connector packages the
# showcase imports were previously built only by accident — through
# dogfood's dependency chain, which broke when dogfood moved to
# packages/qa/ (#3037).
- name: Build workspace packages
run: pnpm exec turbo run build --filter='./packages/*'
run: pnpm exec turbo run build --filter='./packages/*' --filter='./examples/*^...'

- name: Type check example apps
run: pnpm --filter './examples/*' run typecheck
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/spec-liveness-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ name: Spec Liveness Check
#
# ADR-0054 (prove-it-runs): bound high-risk 'live' properties must carry a `proof`
# pointing to a dogfood test that declares the matching `@proof:` tag. The gate also
# triggers on packages/dogfood/** so deleting/renaming a proof re-runs this check and
# triggers on packages/qa/dogfood/** so deleting/renaming a proof re-runs this check and
# the dangling reference is caught (the proof files live outside packages/spec/).

on:
pull_request:
types: [opened, synchronize, reopened]
paths:
- 'packages/spec/**'
- 'packages/dogfood/**'
- 'packages/qa/dogfood/**'

permissions:
contents: read
Expand Down
2 changes: 1 addition & 1 deletion content/docs/permissions/authorization.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ Five mechanisms — four CI-time, one runtime — make the security posture a
`--update-access-matrix` — the snapshot's git diff is the review artifact.
Opt-in, format, and workflow: **[Access-Matrix Snapshot Gate](/docs/permissions/access-matrix)**.

- **Conformance matrix** (`packages/dogfood/test/authz-conformance.matrix.ts`,
- **Conformance matrix** (`packages/qa/dogfood/test/authz-conformance.matrix.ts`,
ADR-0056 D10): every authorization primitive sits in exactly one honest
state — `enforced` (must name its enforcement site; high-risk rows must
reference an end-to-end dogfood proof), `experimental`, or `removed`. A new
Expand Down
2 changes: 1 addition & 1 deletion content/docs/permissions/delegated-administration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Writes to the governed tables (`sys_user_position`,
| No scope, no admin | Holders of plain CRUD on the RBAC tables with **no** scope are refused: administration is a scoped capability now, not a side effect of table access |

Every rule above is exercised end-to-end by the showcase permission zoo
(`packages/dogfood/test/showcase-permission-zoo.dogfood.test.ts`): the
(`packages/qa/dogfood/test/showcase-permission-zoo.dogfood.test.ts`): the
in-subtree allowlisted assignment passes with a `granted_by` stamp; the
out-of-subtree anchor, the off-allowlist grant (to self), and the
`manageBindings: false` binding write are each refused.
Expand Down
2 changes: 1 addition & 1 deletion docs/adr/0054-runtime-proof-for-authorable-surface.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ misbehaves at runtime.

A metadata-driven platform whose authors are AI cannot ship unproven liveness
for the primitives that matter. The static pointer must be upgradable to a
**runtime proof** — a [`@objectstack/dogfood`](../../packages/dogfood) test that
**runtime proof** — a [`@objectstack/dogfood`](../../packages/qa/dogfood) test that
authors the property against the real, in-process stack and asserts the runtime
result.

Expand Down
4 changes: 2 additions & 2 deletions docs/adr/0060-conformance-ledger-platform-pattern.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,6 @@ indirection.
## References

ADRs 0020, 0049, 0054, 0056 (D10), 0058 (D7). Issue #1887. Existing instances:
`packages/dogfood/test/authz-conformance.{matrix,test}.ts`,
`packages/dogfood/test/expression-conformance.{ledger,test}.ts`. Helper home:
`packages/qa/dogfood/test/authz-conformance.{matrix,test}.ts`,
`packages/qa/dogfood/test/expression-conformance.{ledger,test}.ts`. Helper home:
`packages/verify/`. Target surface: `packages/spec/src/data/validation.zod.ts`.
8 changes: 4 additions & 4 deletions docs/adr/0076-objectql-core-tiering.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ADR-0076: objectql is the data engine — relocate metadata management (protocol) out of it; enforce the boundary; defer the engine repo-split

**Status**: Proposed (2026-06-28, rev. 9) — D1–D12 below. D9 step-1 (interface segmentation) shipped in #2429; OQ#7 resolved (keep `metadata-protocol` name). rev.9 adds **D12 (honest capabilities** — discovery must not report stub/fallback services as real; the analytics fallback + dev stubs are marked honestly, not deleted) and corrects the D10 analytics note (deliberate fallback + `replaceService`, not a collision). — v12 assessment. Verified 2026-07-16: D1 (metadata-protocol extraction + back-compat re-export), D2 (core-boundary ratchet test), D9-step1 (segmented protocol interfaces) confirmed in code; D3 capability/profile contract unbuilt; D12 schema half landed but runtime enforcement missing (`http-dispatcher.ts` `svcAvailable` still hardcodes `status:'available'` for every service); D7/D10/D11-decomposition deferred as designed.
**Status**: Proposed (2026-06-28, rev. 9) — D1–D12 below. D9 step-1 (interface segmentation) shipped in #2429; OQ#7 resolved (keep `metadata-protocol` name). rev.9 adds **D12 (honest capabilities** — discovery must not report stub/fallback services as real; the analytics fallback + dev stubs are marked honestly, not deleted) and corrects the D10 analytics note (deliberate fallback + `replaceService`, not a collision). — v12 assessment. Verified 2026-07-16: D1 (metadata-protocol extraction + back-compat re-export), D2 (core-boundary ratchet test), D9-step1 (segmented protocol interfaces) confirmed in code; D3 capability/profile contract unbuilt; D12 framework side landed in #3028 (standardized `__serviceInfo` marker — OQ#11 — honored by BOTH discovery builders; `svcAvailable` no longer hardcodes `available`; analytics fallback reports `degraded`, `/realtime` no longer advertised) — the console-side consumer update ("trust only `handlerReady:true`") remains for the cross-repo window; D7/D10/D11-decomposition deferred as designed (OQ#9/OQ#10 resolved below, #3037).
**Deciders**: ObjectStack Protocol Architects
**Builds on**: [ADR-0005](./0005-metadata-customization-overlay.md) (sys_metadata overlay substrate), [ADR-0025](./0025-plugin-package-distribution.md) (plugin package distribution), [ADR-0033](./0033-ai-assisted-metadata-authoring.md) (open-core boundary), [ADR-0048](./0048-cross-package-metadata-collision.md) (package id is the addressing unit), [ADR-0066](./0066-unified-authorization-model.md) (secure-by-default, posture-gated bypass)
**Consumers**: **new** `@objectstack/metadata-protocol` (receives `protocol` + `sys-metadata-repository` + `metadata-diagnostics`), `@objectstack/objectql` (loses protocol → becomes a lean data engine; keeps a back-compat re-export), `@objectstack/metadata-core` (gains the `SysMetadataEngine` interface), `@objectstack/plugin-security`, `@objectstack/plugin-sharing`, `@objectstack/spec`, and out-of-tree embedders — notably `../objectbase` (its `gateway`).
Expand Down Expand Up @@ -204,6 +204,6 @@ import { SecurityPlugin } from '@objectstack/plugin-security';
6. **Data-facade home** — does the `DataProtocol` impl live in the engine-adjacent transport layer / `rest`, or a small `@objectstack/protocol-data`? (It is thin and transport-shaped.)
7. **Metadata package name (post-segmentation)** — **Resolved: keep `@objectstack/metadata-protocol`** (already published; renaming has ~0 benefit and real churn). The `protocol` suffix is a low-cost naming exception — the contract is in `@objectstack/spec/api`; a README note in the package should clarify impl-vs-contract.
8. **Per-domain versioning** — once segmented, do capability protocols get independent version markers / a `getCapabilities()` discovery method?
9. **dispatcher vs rest-server overlap** — are `runtime/http-dispatcher` (~3.8k) and `rest/rest-server` (~5.1k) redundant central transport layers? Consolidate or delineate (D11).
10. **Validate multi-adapter** — write a second `IHttpServer` adapter (thin Workers/Express) to prove the port is free of Hono-isms before relying on it (D11).
11. **D12 stub marker** — standardise the self-identifying marker: reuse `_dev`, introduce `__stub: true`, or a richer per-service capability descriptor? (Decide when D12 is implemented.)
9. **dispatcher vs rest-server overlap** — **Resolved (audit, #2462): delineate, do not merge.** They partition the API surface by domain rather than duplicating it: in a standard boot REST owns data/meta/batch/import-export/search/email/forms/sharing/reports/approvals/security, the dispatcher bridge owns mcp/ai/graphql/keys/automation/actions/storage/i18n/analytics/health/ready. Only **discovery** and **packages** are genuinely double-mounted (first-registration-wins — the D11 smell to fix), and the dispatcher's `/data`/`/meta`/`/ui`/`/notifications`/`/share-links`/`/security` `dispatch()` branches are **not mounted** in a standard boot (they are live only when `HttpDispatcher` is used directly as a callable by out-of-tree adapters). D11 worklist: (1) kill or delegate the dead callable branches; (2) give packages + discovery a single owner; (3) extract per-domain handlers; (4) unify env-resolution on the injected `KernelResolver` seam.
10. **Validate multi-adapter** — **Resolved (validated, #2462): the port is free of hard Hono-isms.** A zero-dependency `node:http` reference adapter (`@objectstack/http-conformance`, private QA gate under `packages/qa/`) runs the dispatcher bridge + REST generator unchanged; a cross-adapter conformance suite (40 assertions incl. `/data` CRUD roundtrip, `:param` routing, 404/405 semantics, SSE streaming, discovery) passes identically on both adapters. Findings: the Hono adapter's Host-header backfill is adapter-local (a Fetch-API artifact, not a port leak); all remaining Hono coupling is confined to the `getRawApp()` escape hatch (metadata HMR, cloud-connection/marketplace routes, static/SPA + CORS + Server-Timing), whose consumers feature-detect and degrade. Follow-up for D11: codify the two soft extensions consumers already rely on (`res.write`/`res.end` for SSE, `getPort()`) and 404/405 semantics into the `IHttpServer` contract.
11. **D12 stub marker** — **Resolved (#2462): standard `__serviceInfo` self-descriptor** (`ServiceSelfInfoSchema` in `spec/api/discovery.zod.ts`, read via `readServiceSelfInfo()`), with plugin-dev's legacy `_dev: true` normalized to `{ status: 'stub', handlerReady: false }`. Both discovery builders honor it; the analytics fallback self-identifies as `degraded`.
Original file line number Diff line number Diff line change
Expand Up @@ -113,5 +113,5 @@ Execution landed across both repos; differences from the proposal are noted inli
- **§6 guardrails** — `@objectstack/lint` `validate-semantic-roles`: undeclared `Field.group` reference, declared-but-unreferenced group, dangling `stageField`/`highlightFields` pointers; plus `field-group-shadowed` (added by the #2548 follow-up): a group whose every visible member is hoisted into the detail highlight strip (or is the record title) renders on forms but never on detail pages.
- **PR4 (legacy-path deletion)** — objectui#2546 removed the monolith `DetailView` branch in `RecordDetailView` together with the `detail.renderViaSchema` kill-switch and the `?renderViaSchema=0` debug param; schema-driven is the only path.
- **Cross-surface consumption** — kanban default card fields ride `highlightFields` (objectui#2541), alongside grids/lists/detail strip.
- **Verification** — parse: `@objectstack/spec` suite; served pipeline: `packages/dogfood/test/semantic-roles.dogfood.test.ts`; real-backend browser pass over the four detail shapes (grouped / ungrouped / `stageField: false` / related-heavy): framework#3019, runbook + results in `docs/audits/2026-07-adr-0085-detail-shapes-browser-verify.md`, and a permanent Playwright spec in `examples/app-showcase/e2e/detail-shapes.spec.ts`.
- **Verification** — parse: `@objectstack/spec` suite; served pipeline: `packages/qa/dogfood/test/semantic-roles.dogfood.test.ts`; real-backend browser pass over the four detail shapes (grouped / ungrouped / `stageField: false` / related-heavy): framework#3019, runbook + results in `docs/audits/2026-07-adr-0085-detail-shapes-browser-verify.md`, and a permanent Playwright spec in `examples/app-showcase/e2e/detail-shapes.spec.ts`.
- **Consequence surfaced by the browser pass** — because detail bodies hide strip fields, a fully-highlighted group silently disappears from detail pages. Judged working-as-intended (one curated list, every surface) but author-surprising — hence the `field-group-shadowed` warning and the semantic-zoo fixture keeping one non-highlighted member per group.
4 changes: 2 additions & 2 deletions docs/adr/0095-authz-kernel-tenant-layer-and-posture-ladder.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ The chain is strictly serial — `D1 → D2 → D3` (#2920's B1 → B2 → B4)
each step lands only behind the snapshot gate:

0. **Gate first.** The existing conformance matrix
(`packages/dogfood/test/authz-conformance.matrix.ts`) is integration-layer;
(`packages/qa/dogfood/test/authz-conformance.matrix.ts`) is integration-layer;
before D1 begins, an equivalent `role × object × expected-visible-rows`
snapshot must run at the **unit layer** (inside `plugin-security`'s test
suite) so the loop is minutes, not a dogfood boot.
Expand Down Expand Up @@ -315,7 +315,7 @@ each step lands only behind the snapshot gate:
(`tenant_isolation` wildcard policy),
`packages/core/src/security/resolve-authz-context.ts`,
`packages/plugins/plugin-security/src/permission-evaluator.ts`,
`packages/dogfood/test/authz-conformance.matrix.ts`.
`packages/qa/dogfood/test/authz-conformance.matrix.ts`.
- ADR-0002 (physical isolation), ADR-0066 (precedence rule this ADR makes
true; superuser bypass gate), ADR-0073 (claim scoped by this ADR),
ADR-0093 (`tenancy` service consumed by Layer 0).
2 changes: 1 addition & 1 deletion docs/adr/0096-execution-surface-identity-admission.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,4 +271,4 @@ Everything else — the D3 signature migration, strict-mode-ON-in-CI per package
- framework#2980 / #2981 / #2982 — the confirmed-exploitable instances the Evidence sweep surfaced (reports IDOR + scheduled-report RLS bypass; knowledge/RAG retrieval fall-open; bulk-write OWD gap) — fixed independently; this ADR's motivating evidence
- `packages/plugins/plugin-security/src/security-plugin.ts` — the empty-principal seam (`:626` middleware, `:1689` getReadFilter) (D5's target)
- `packages/runtime/src/http-dispatcher.ts` — `buildActionEngineFacade` (both facades), `invokeBusinessAction`
- `packages/dogfood/test/authz-conformance.matrix.ts` — the ADR-0056 D10 matrix D4 extends
- `packages/qa/dogfood/test/authz-conformance.matrix.ts` — the ADR-0056 D10 matrix D4 extends
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ objectui#2546 deleted the non-schema-driven `DetailView` monolith branch, so
`RecordDetailView` now has exactly **one** path: an authored
`PageSchema(pageType='record')` when assigned, else `buildDefaultPageSchema()`
synthesis — both rendered through `<SchemaRenderer>`. Unit + DOM suites and the
served-metadata proof (`packages/dogfood/test/semantic-roles.dogfood.test.ts`)
served-metadata proof (`packages/qa/dogfood/test/semantic-roles.dogfood.test.ts`)
cover parse and serialization, but neither renders a page in a browser against a
live backend. This pass closes that gap for the four shapes the PR called out.

Expand Down
2 changes: 1 addition & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export default [
// backward-compat fixture (#2089) and are intentional.
{
files: ['examples/**/*.{ts,tsx,mts,cts}', 'packages/apps/**/*.{ts,tsx,mts,cts}'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/downstream-contract/**'],
ignores: ['**/node_modules/**', '**/dist/**', 'packages/qa/downstream-contract/**'],
languageOptions: {
parser: tsParser,
parserOptions: { ecmaVersion: 'latest', sourceType: 'module' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* *static*-checked by the spec suite; these two objects prove the SERVED
* pipeline (defineStack → artifact → register → REST serialization) neither
* strips nor mangles them. Guarded by
* `packages/dogfood/test/semantic-roles.dogfood.test.ts`.
* `packages/qa/dogfood/test/semantic-roles.dogfood.test.ts`.
*
* Two objects, two role postures:
* - `SemanticZoo` authors the full canonical role set (highlightFields,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// expected-visible-rows` snapshot: every cell must match across the Layer 0
// extraction EXCEPT the four deltas the architect accepted (all W1-class,
// all toward stronger/correcter isolation — see below). The existing
// conformance matrix (`packages/dogfood/test/authz-conformance.matrix.ts`)
// conformance matrix (`packages/qa/dogfood/test/authz-conformance.matrix.ts`)
// proves this end-to-end through a real app boot (minutes). This file is the
// UNIT-LAYER equivalent so the loop is seconds: it drives the real
// SecurityPlugin CRUD middleware with the real seeded permission sets and
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ keeps the green gate green):
# 1. In plugin-security/src/security-plugin.ts, disable the pre-image check:
# change `if (` to `if ( false &&` at the `(opCtx.operation === 'update' …` block.
pnpm --filter @objectstack/plugin-security build # package resolves to dist
cd packages/dogfood && npx vitest run test/rls-fixture.dogfood.test.ts -t "owner-scoped"
cd packages/qa/dogfood && npx vitest run test/rls-fixture.dogfood.test.ts -t "owner-scoped"
# → rls_note flips to [rls-hole]: "GET 404 yet MUTATED it by id (PATCH 200)".
git checkout -- ../plugins/plugin-security/src/security-plugin.ts && pnpm --filter @objectstack/plugin-security build
```
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import { checkLedger } from '@objectstack/verify';
import { AUTHZ_CONFORMANCE, type AuthzPrimitive } from './authz-conformance.matrix.js';

const HERE = dirname(fileURLToPath(import.meta.url));
// packages/dogfood/test → repo root.
const REPO_ROOT = join(HERE, '../../..');
// packages/qa/dogfood/test → repo root.
const REPO_ROOT = join(HERE, '../../../..');

// ── #2567 ratchet — static enumeration of anonymous-deny HTTP entry points ──
//
Expand Down
Loading
Loading