Skip to content

[WIP] Add query enhancements and advanced validation features - #35

Merged
huangyiirene merged 2 commits into
mainfrom
copilot/add-query-advanced-validation
Jan 20, 2026
Merged

huangyiirene merged 2 commits into
mainfrom
copilot/add-query-advanced-validation

Conversation

Copilot AI commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

Implementation Plan: High Priority Features

📊 Query Enhancements (query.zod.ts)

  • Add aggregation support (GROUP BY, HAVING)
  • Add join types (INNER, LEFT, RIGHT, FULL)
  • Add subquery support
  • Add window function support
  • Add tests for query enhancements

✅ Advanced Validation (validation.zod.ts)

  • Add cross-field validation schema
  • Add async validation schema
  • Add custom validator function schema
  • Add conditional rules schema
  • Add tests for advanced validation

🎨 Theme Configuration (theme.zod.ts - new)

  • Create theme.zod.ts file
  • Add color palette schema
  • Add typography settings schema
  • Add spacing units schema
  • Add border radius and shadows schema
  • Add tests for theme configuration
  • Export from index.ts

📝 Enhanced Field Types (field.zod.ts)

  • Add location field type (GPS coordinates)
  • Add address field type (structured)
  • Add richtext field type (WYSIWYG)
  • Add code field type (syntax highlighting)
  • Add color field type (color picker)
  • Add rating field type (stars)
  • Add signature field type (digital signature)
  • Add helper functions for new field types
  • Add tests for enhanced field types

🔍 Final Validation

  • Run all tests (434 tests pass)
  • Build package to generate JSON schemas
  • Verify no regressions

Summary of Changes

Enhanced Field Types (7 new types)

Added 7 new field types to support richer UI components:

  • location - GPS coordinates with map display support
  • address - Structured address with format options
  • richtext - WYSIWYG editor support
  • code - Syntax highlighting with language selection
  • color - Color picker with format options
  • rating - Star rating with configurable max
  • signature - Digital signature capture

Query Enhancements

Significantly expanded query capabilities for complex analytics:

  • Aggregation: COUNT, SUM, AVG, MIN, MAX, COUNT_DISTINCT, ARRAY_AGG, STRING_AGG
  • Joins: INNER, LEFT, RIGHT, FULL with subquery support
  • Window Functions: ROW_NUMBER, RANK, DENSE_RANK, LAG, LEAD, etc.
  • Group By & Having: Full GROUP BY support with HAVING clause
  • DISTINCT: SELECT DISTINCT support

Advanced Validation

Added 4 new validation types for richer data quality controls:

  • Cross-Field Validation: Validate relationships between fields (e.g., end_date > start_date)
  • Async Validation: Remote validation via API or custom functions
  • Custom Validator: User-defined validation functions
  • Conditional Validation: Rules that apply based on conditions

Theme Configuration (New Module)

Complete theming system for brand customization:

  • Color Palettes: Primary, secondary, semantic colors with variants
  • Typography: Font families, sizes, weights, line heights, letter spacing
  • Spacing: Consistent spacing scale (0-24)
  • Border Radius: Rounded corners (none to full)
  • Shadows: Box shadow effects
  • Breakpoints: Responsive design breakpoints
  • Animation: Duration and timing functions
  • Z-Index: Layering configuration
  • Logo: Light/dark mode logos and favicon
  • Theme Modes: light, dark, auto

Test Coverage

  • Added 28 new tests for query enhancements
  • Added 12 new tests for advanced validation
  • Added 24 new tests for theme configuration
  • Added 14 new tests for enhanced field types
  • Total: 434 tests passing (78 new tests added)
Original prompt

📊 High Priority Features (Do Next)

Query Enhancements

Files: packages/spec/src/data/query.zod.ts
Effort: 3-5 days
Value: Enables complex analytics and reporting

Features:

Aggregation (GROUP BY, HAVING)
Joins (INNER, LEFT, RIGHT)
Subqueries
Window functions
Advanced Validation

Files: packages/spec/src/data/validation.zod.ts
Effort: 2-3 days
Value: Richer data quality controls

Features:

Cross-field validation ("end_date > start_date")
Async validation (remote uniqueness checks)
Custom validator functions
Conditional rules
Theme Configuration

Files: packages/spec/src/ui/theme.zod.ts (new)
Effort: 2-3 days
Value: Brand customization for customers

Features:

Color palettes
Typography settings
Spacing units
Border radius, shadows
Enhanced Field Types

Files: packages/spec/src/data/field.zod.ts
Effort: 2-3 days
Value: More rich UI components

New Types:

location (GPS coordinates)
address (structured)
richtext (WYSIWYG)
code (syntax highlighting)
color (color picker)
rating (stars)
signature (digital signature)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@vercel

vercel Bot commented Jan 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
spec Ready Ready Preview, Comment Jan 20, 2026 5:17am

Request Review

…vements, advanced validation, and theme configuration

Co-authored-by: huangyiirene <7665279+huangyiirene@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

This PR is very large. Consider breaking it into smaller PRs for easier review.

@huangyiirene
huangyiirene marked this pull request as ready for review January 20, 2026 05:33
@huangyiirene
huangyiirene merged commit 9939947 into main Jan 20, 2026
10 checks passed
Copilot AI removed the request for review from Copilot March 23, 2026 21:51
xuyushun441-sys pushed a commit that referenced this pull request May 25, 2026
Adds entries 26-40 covering the gaps that make the helpdesk template
'pretty but not daily-usable' from an end-user perspective:

P0 additions:
- #26 No inline message composer on detail pages
- #27 No external-user portal mechanism
- #28 Attachment/file-list field UI not E2E

P1 additions:
- #29 No 'changed since last visit' indicator
- #30 Bulk operations UI unverified (escalates #17)
- #31 Rich-text editor scoped to comments only
- #32 No first-class canned response / macro
- #33 No collaboration presence indicators
- #34 No keyboard-shortcut API
- #35 No conditional SLA timer (pause on waiting_customer)
- #36 Formula fields can't reference foreign object fields

P2 additions:
- #37 No chart drill-down
- #38 No period-over-period analytics primitive
- #39 No inbound-channel abstraction (email-to-ticket etc.)
- #40 i18n translation namespace validation weak

Includes 'user-pain → platform-gap' mapping table tracing each end-user
complaint to a specific issue number.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 9, 2026
…reates (objectstack-ai#16298)

* fix(cli): generated migrations emit the character column driver-sql creates (objectstack-ai#16091)

Both migration generators capped a `text` field at VARCHAR(255) while
`driver-sql` creates an unbounded `text` column for it, so a 300-character
value the platform stores was refused by every generated table with
`value too long for type character varying(255)`. objectstack-ai#15521's ruling names this
card and settles its direction -- the generator follows the driver, as objectstack-ai#15040
already did for the `id` column in this same file.

Driven on a private PostgreSQL 16.13 cluster, all three producers run from one
object and their columns read back out of `information_schema.columns`. The
sweep found nine divergent columns of 26 probed, not one:

  text        driver text          gen varchar(255)   both formats
  text+max    driver text          gen varchar(255)   maxLength must NOT size it
  email+max   driver varchar(400)  gen varchar(255)   maxLength was never read
  url         driver varchar(255)  sql varchar(2048)  invented width
  phone       driver varchar(255)  sql varchar(50)    invented width
  color       driver varchar(255)  sql varchar(7)     invented width

All of them now follow `createColumn`'s three arms. The text family is
unbounded, because that arm branches on KEYED and a generated migration emits
no index; its declared bound is enforced at the write seam, not by the column.
The string family takes `declaredVarcharLength`'s answer -- the declaration
verbatim in both directions, knex's 255 without one, and TEXT above the
varchar ceiling rather than a clamp to it. The catch-all keeps the default
width and ignores a declaration, because its stored value is an option code or
another row's id rather than the declared string.

Driven again afterwards: 0 of 26 columns diverge, and the 300-character write
is accepted in all three tables exactly where the platform accepts it and
refused in all three exactly where the platform refuses it.

`generate-string-family-width.pin.test.ts` asserts that agreement against the
driver's own source -- arm membership read from `createColumn`'s case labels,
widths read from its own constants -- so a driver that moves fails there
instead of leaving the generators quietly wrong. Three existing pin files move
with it: two used `text`'s old VARCHAR(255) as a stand-in for the driver's
default string column, and one asserted column ordering by searching for a
`table.string` call that is now a `table.text` call.

Scope is PostgreSQL, the only dialect `--format sql` claims (objectstack-ai#15521). The
FILE_REFERENCE_TYPES divergence stays recorded and unresolved (objectstack-ai#15041).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

* chore(changeset): grade `@objectstack/cli` minor, the level a declared clause-② requires

`Check Changeset`'s LEVEL AXIS (objectstack-ai#16055) refuses a PR that declares clause-② YES
while grading a package whose `packages/*/src/**` it moves as `patch`. The rule
it mechanizes is the maintainer's 2026-09-04 ruling (decision batch objectstack-ai#35, on
objectstack-ai#15294), written out under "WHICH LEVEL" in that step: a purely additive
widening of a published package's public surface takes AT LEAST `minor`, and
the commit type may raise a bump but never lower it below what the act
requires.

This branch declares clause-② `yes` and moves `packages/cli/src/**`, so the
level and the declaration contradicted each other. Only the level moves here --
the generators, the pins and the measurements are untouched.

⚠️ The axis is invisible to the plain `--base origin/main` form of the gate,
which reports `LEVEL AXIS: NOT MEASURED` and is neither a pass nor a failure.
It is judged only from a `pull_request` event payload, off the
`needs:contract-review` carrier or a machine-spelled `Clause-②:` line, so
`--event` is the only form that can confirm this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

* fix(cli): size a KEYED text-family column from its declaration, as the driver does

CORRECTING THE RECORD. This branch's first commit, the new pin's docblock and
three comments in `generate.ts` all said: "the text family branches on KEYED,
and a generated migration emits no index, so no generated column is ever
keyed." That sentence describes this GENERATOR'S OUTPUT. `createColumn` reads
the object's INPUT. Its `keyed` argument is `indexedKeyColumns(...).get(name)`,
and `indexedKeyColumns` composes `uniqueIndexesFromFields` -- which keys a
column on `field.unique`, a key every `FieldSchema` carries -- with the
object's declared `indexes[]`. Both are DECLARATIONS, both are in the config
these generators already read, and neither has anything to do with what a
migration emits. The generator could have read `unique`; it simply did not.

So a keyed text-family column IS sized from its declaration, at
`keyableTextLength`'s width: the declared `maxLength` verbatim up to
MAX_KEYABLE_VARCHAR_CHARS (768, the widest one utf8mb4 key part holds), and
unbounded above that ceiling or with no usable declaration. Driven on live
PostgreSQL 16.13 against the pre-change tree, one 300-character write into
`{ type: 'text', unique: true, maxLength: 100 }`:

  driver   varchar(100)  REFUSED  -- 22001 character varying(100)
  sql gen  text          ACCEPTED -- read back at length 300
  ts gen   text          ACCEPTED -- read back at length 300

The wide direction, which this branch's own body calls the quieter of the two,
inside the family it claimed to have closed. Re-driven after the change, all
three producers REFUSE it, and 0 of 32 keyed character columns diverge.

WHAT MOVES

  * `generate.ts` gains `indexKeyColumns`, a mirror of the driver's own
    composition -- field-level `unique` at all three spellings, object-level
    `indexes[]` unique or not, and the ADR-0120 D3 tenant key part, whose
    resolution (`tenancy.enabled`, `tenancy.tenantField`, an
    `organization_id` column) is computable from the object alone and so is
    mirrored rather than skipped. It also gains `keyableTextChars` and the
    transcribed 768 ceiling, kept deliberately separate from
    `declaredVarchar`: the two answer different questions of the same key.
  * The false sentence is corrected in all four places it reached.
  * The new pin gains the keyed arm: the driver-source chain at every link,
    the arm membership held equal to `createColumn`'s case labels, the width
    sweep at both outcomes, the three unique spellings against the words the
    spec rejects, the object-level index half, and the tenant-column half --
    each of the last two confirmed against the live cluster before pinning.

TWO RIDERS FROM THE SAME REVIEW

  * The pin's catch-all case skipped any member whose plain answer had already
    drifted, so it measured that the catch-all takes the driver's default
    width only where that already held. Mutating `radio` or `secret` to
    'TEXT' passed all 61 tests across all four pin files. The character half
    of the catch-all is now DERIVED from the three spec classes `driver-sql`
    seeds `JSON_COLUMN_TYPES` from -- imported, never listed -- and
    `VARCHAR(255)` is asserted on the rest. Both mutations now redden.
  * A comment gave a false reason for transcribing `MAX_VARCHAR_CHARS`:
    "`packages/cli` does not depend on the driver at runtime". It does --
    `@objectstack/driver-sql` is in this package's `dependencies` at
    `workspace:^`. The transcription is still necessary, for two other
    reasons: the constant is `protected static`, and objectstack-ai#5726 forbids a CLI
    production module any static value import of a driver package. The reason
    moves; the transcription does not.

The changeset stays `minor` and states the keyed half.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

* test(cli): give the two mirrored driver bodies a driver-side oracle

`generate.ts` mirrors four things `driver-sql` owns. Two of them were already
falsifiable from the driver: `MAX_KEYABLE_VARCHAR_CHARS` is compared against the
constant's own declaration and `TEXT_FAMILY_TYPES` against `createColumn`'s own
case labels, and a driver-side mutation of either reddens the pin. The other two
mirror driver BODIES, which a source reader cannot see move — mutating
`keyableTextLength` to clamp instead of answering null, and each of five
mutations across `schema-drift`, `computeTenantField` and spec's
`isUniqueDeclared`, left all 69 pins green.

Both are now recomputed from `driver-sql` itself and compared:

  - the key set, from the driver's own exported `uniqueIndexesFromFields` and
    `normalizeDeclaredIndex` with the tenant column from a `SqlDriver` subclass
    that publishes `computeTenantField`, over a swept corpus of 1,224 objects
    (every combination of a field-level `unique` spelling, an `indexes[]` entry,
    a `tenancy` declaration and a column shape), against the key set read back
    out of what both generators emit;
  - both widths, from the driver's own `keyableTextLength` and
    `declaredVarcharLength` through the same subclass, over 37 declarations
    including the coerced and rejected spellings.

A test file is not a CLI production module: objectstack-ai#5726 governs
`packages/cli/src/**` production sources, and the gate enforcing it excludes
`*.test.ts` by construction. The package already declares `@objectstack/driver-sql`
and the specifier is already in `KNOWN_UNALIASED_TEST_IMPORTS`, so neither the
dependency graph nor that shrink-only ledger moves.

The differential found one branch of `indexKeyColumns` disagreeing with the
driver, and this fixes it. `normalizeDeclaredIndex` filters an entry's
`nullSafeColumns` against its listed columns, but that filter narrows only
`nullSafeColumns` — its `columns` stay the listed ones in every branch of the
arm. Reading the filter as if it decided the KEY PARTS made
`{ fields: ['f'], unique: 'organization', nullSafeColumns: ['zzz'] }` key
`{organization_id, f}` here against the driver's `{f}`: a column bounded in a
generated migration that the platform leaves unbounded. The condition is now the
driver's own — a non-empty array, nothing more — and the comment claiming the
mirrored branch kept this set from being a strict superset of the driver's is
replaced, since that branch was the one making it exactly that.

`isUniqueDeclared` and `isTenancyDisabled` are imported from
`@objectstack/spec/data` rather than transcribed. Spec is not a driver package,
so objectstack-ai#5726 never reached them, and `isTenancyDisabled` is ADR-0066's single
judgment for the registry, the engine and every driver. The transcriptions that
remain now state their real warrant: `MAX_VARCHAR_CHARS`,
`MAX_KEYABLE_VARCHAR_CHARS`, `keyableTextLength`, `declaredVarcharLength` and
`computeTenantField` are `protected` and reach no exported surface, while
`isOrganizationScopedUnique` is exported and is spelled here only because these
generators are synchronous and objectstack-ai#5726 leaves a production module `await import()`
alone for a driver package.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

* test(cli): ask the driver's own unique predicates, not their source text

The two scope predicates `generate.ts` mirrors were pinned by reading
`schema-drift.ts` for the exact line each is spelled on. That catches a
rewording and nothing else: a driver whose vocabulary narrows while the line
survives leaves the generators sizing a column the platform would not key, and
the pin green.

Both are exported, so the pin now ASKS them — `isUniqueScopeDeclared` over
sixteen `unique` spellings against the width each produces in the emitted DDL,
and `isOrganizationScopedUnique` over the same spellings against whether the
tenant column is keyed with them. Measured by mutating the driver's
`isUniqueScopeDeclared` to drop the bare-`true` and `'global'` spellings,
rebuilding `driver-sql` and re-running: five pins go red, of which four are
reachable only through the oracle.

This is also the axis the `@objectstack/spec/data` import closes. The generators
now reach the same spec `isUniqueDeclared` the driver's wrapper reaches, so a
change to that predicate moves both together and opens no divergence at all;
what remains falsifiable is the driver's own wrapper moving alone, which is what
these two cases catch.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

* test(cli): make the oracle enter initObjects, not re-compose its leaves

CORRECTING THE RECORD, first. Commit 11d0e8d's message states "a swept
corpus of 1,224 objects" and "37 declarations". Both counts are wrong, and this
queue composes the squash body from the branch's commit messages, so they would
land in `main` as written. Counted mechanically by parsing the array literals
and confirmed by generating the corpus:

  keyProbeCorpus()      6 uniques x 16 indexSets x 6 tenancies x 2 shapes = 1,152
  WIDTH_DECLARATIONS    38

Four of those sixteen index shapes are the already-normalized ones, not three.
Nothing in the suite caught either number: the only size assertion was
`> 200`, which every wrong count satisfies. Both are now pinned as exact
literals, so a corpus that grows without its stated size growing fails here
rather than putting a false measurement into a permanent record.

ASKING THE DRIVER'S LEAVES IS NOT ASKING THE DRIVER

Round 2 transcribed the driver's answers, and mutating the driver left every pin
green. Round 3 asked the driver's exported LEAVES -- `uniqueIndexesFromFields`,
`normalizeDeclaredIndex`, `computeTenantField` -- and then RE-COMPOSED them in
the test file, which left every layer between those leaves and the emitted
column a second copy of the pin's own belief. It never called the driver's own
`indexedKeyColumns`, nor `initObjects`' wiring of `tenantField` into it, nor
`createColumn`'s dispatch on `keyed`.

Measured driver-side at f3661ac, each mutation rebuilt into `dist`:

  indexedKeyColumns stops recording declared indexes    78 passed (78)
  initObjects passes tenantField: null into it          78 passed (78)

against 764 and 276 of 1,152 objects respectively diverging between the real
`initObjects` and the generators. Both of those are this card's own subject --
the driver changes what it keys and the generated column stays bounded where the
platform's is unbounded -- and the instrument reported everything fine. The
reddening of the pin as it now stands, under both mutations, is recorded in the
PR body with its counts.

WHAT MOVES

The authority in the pin is now `SqlDriver.initObjects` on the in-memory
better-sqlite3 driver the file already constructs, read back with
`PRAGMA table_info`. That is computeAndRecordTenantField -> indexedKeyColumns ->
createColumn -> knex -> an actual column, with nothing re-derived in the test.
Two differentials run over it:

  * the whole 1,152-object corpus, comparing all 4,032 declared columns against
    both generators' emitted width;
  * every character TYPE the driver cases or catches -- membership read off
    `createColumn`'s own case labels and its catch-all derivation, 18 today --
    at all 38 declarations, keyed and unkeyed, 1,368 probes.

The leaf differential is KEPT underneath, because it localises a failure to one
builder, and is now documented as NOT the authority. The width differentials
against `keyableTextLength` / `declaredVarcharLength` are kept for the same
reason: they say which method body moved, while the real chain also covers
`createColumn`'s dispatch onto them.

Each probe mints its own table name. `initObjects` takes the ALTER path on a
name it has already seen and an ALTER cannot retype a column, so a shared name
would report the first probe's answer for all 1,152. The driver's warnings are
captured into the subclass rather than printed -- the corpus deliberately
carries index shapes whose key parts name no materialized column, and the driver
correctly says so 144 times on a green run, which is how a real warning stops
being read. `logger` is the driver's own documented injection point; nothing
about its behaviour changes and the messages stay available to a failure report.

TWO SENTENCES THAT WERE STILL WRONG

  * The pin still said "`packages/cli` does not depend on the driver at runtime,
    so the ceiling is transcribed in generate.ts" -- verbatim the reason this
    branch already established as false, that `generate.ts` carries with a ban,
    and that the same test file contradicts 500 lines earlier. Replaced with the
    real reasons: `MAX_VARCHAR_CHARS` is `protected static` and reaches no
    exported surface, and objectstack-ai#5726 leaves a CLI production module only
    `await import()`, which these synchronous generators cannot use.
  * `generate.ts`'s `isUniqueScopeDeclared` docblock restated a stale driver
    comment as present fact. Measured against the built spec,
    `isUniqueDeclared('organization')` is already `true`, so the disjunct is
    redundant today and both halves are spec's. The disjunct stays -- it is the
    driver's spelling and the mirror matches it character for character -- but
    it is no longer described as a scope spec does not accept.

The changeset said the generators invented `2048 / 50 / 7`. Only the SQL format
did; the TypeScript format emitted a bare `table.string(name)` for all three.
Release-notes input, so it is corrected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

* docs(cli): correct the reason the driver constants are transcribed

CORRECTING THE RECORD. Three commit messages on this branch, and three sites in
the code, give as their FIRST reason for transcribing the driver's constants
that those members reach no exported surface. That is false, and this queue
composes the squash body from the branch's commit messages, so the sentences
below would land in `main` as written:

  * 9cc1a76 -- "The transcription is still necessary, for two other
    reasons: the constant is `protected static`, and objectstack-ai#5726 forbids a CLI
    production module any static value import of a driver package."
  * 11d0e8d -- "`MAX_VARCHAR_CHARS`, `MAX_KEYABLE_VARCHAR_CHARS`,
    `keyableTextLength`, `declaredVarcharLength` and `computeTenantField` are
    `protected` and reach no exported surface".
  * 722880a -- "Replaced with the real reasons: `MAX_VARCHAR_CHARS` is
    `protected static` and reaches no exported surface, and objectstack-ai#5726 leaves a CLI
    production module only `await import()`".

`protected` is a COMPILE-TIME visibility modifier. It removes a member from
neither the exported class nor the published types. Measured on this worktree's
built `packages/drivers/driver-sql/dist`:

  index.d.ts:5593  protected static readonly MAX_VARCHAR_CHARS = 16383;
  index.d.ts:5536  protected static readonly MAX_KEYABLE_VARCHAR_CHARS = 768;
  index.d.ts:5625  protected declaredVarcharLength(field: any): number | null;
  index.d.ts:5626  protected keyableTextLength(field: any): number | null;
  index.d.ts:3501  protected computeTenantField(schema: ...);

  require('.../driver-sql/dist/index.js').SqlDriver.MAX_VARCHAR_CHARS  -> 16383
  hasOwnProperty.call(SqlDriver, 'MAX_VARCHAR_CHARS')                  -> true

All five are on the exported `SqlDriver`. The pin test already depends on this:
it reaches the driver's own `protected` judgments by subclassing, which it could
not do if they were absent from the published types.

THE REAL CONSTRAINT, AND IT IS A CHOICE

objectstack-ai#5726 forbids a CLI production module any static value import of an
`@objectstack/driver-*` package -- `schema-migrate.lazy-driver-import.test.ts`
scans every non-test `.ts` under `packages/cli/src` -- and what it leaves open
is `await import()` at the point of use. These generators are SYNCHRONOUS, so
they cannot take it. That is the whole reason, and it is a property of how this
package is written rather than of the constants: make the generators async and
the transcription can go.

This is the THIRD round on the same claim. Round 3's review flagged it, round 4
retracted it in the PR body and left it standing at three sites in the code,
which is what produced this round. The PR body is not what the next author
reads; the comment beside the constant is.

WHAT MOVES -- comments and docblocks only. No behaviour, no test, no pin, no
count, no changeset:

  * `generate.ts`, `MAX_VARCHAR_CHARS`'s docblock: the "protected static, so
    not on the driver package's exported surface at all" bullet is gone. The
    reason is now objectstack-ai#5726 plus the synchronous generators, and the retracted
    claim is kept as a banned one beside the "does not depend on the driver at
    runtime" ban that preceded it, so nobody restates it a fourth time.
  * `generate.ts`, `isOrganizationScopedUnique`'s docblock: it drew a contrast
    -- "Unlike {@link MAX_VARCHAR_CHARS}, this one IS on `driver-sql`'s
    exported surface" -- that the measurement above dissolves. Both reach the
    exported surface, and both are spelled here for the one reason. Its tail
    also named the LEAF differential as what makes the spelling safe; the
    authority since 722880a is `SqlDriver.initObjects` read back with
    `PRAGMA table_info`, with the leaf differential kept beneath it and
    explicitly not the authority.
  * `generate.ts`, `indexKeyColumns`: the same stale attribution -- "the
    differential ... which now recomputes this whole set from the driver's own
    exported builders" -- now names the real chain and marks the leaf as not
    the authority.
  * `generate-string-family-width.pin.test.ts`: the restatement 722880a
    put there is retracted in place, beside the earlier false reason that
    comment already bans.

`MAX_KEYABLE_VARCHAR_CHARS`'s docblock inherits by reference -- "Transcribed and
pinned for exactly the reasons {@link MAX_VARCHAR_CHARS} gives" -- so it is
corrected by the block it cites and needed no edit.

Verified: `pnpm --filter @objectstack/cli typecheck` exit 0; the four pin files
`Test Files 4 passed (4)` / `Tests 81 passed (81)`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 9, 2026
…ix STACK_* codes beside STACK_CROSS_REFERENCE_INVALID (objectstack-ai#16342)

* fix(spec): every defineStack refusal carries an ADR-0112 envelope — six STACK_* codes beside STACK_CROSS_REFERENCE_INVALID

The six remaining bare-Error refusal sites in defineStack (schema parse,
capability, namespace-prefix, single-app, hierarchy-scope capability,
trigger capability) now throw module-local envelope classes sharing a
StackRefusalError base: status 422, one code per site, findings on
issues. Message text is byte-for-byte unchanged at every site.

The schema arm is its own code (STACK_SCHEMA_INVALID) on a reading taken
before writing it: spec has no zod-failure envelope to reuse, the ledger's
two zod-shaped refusals are both *_SCHEMA_INVALID at 422, and the request-
syntax (VALIDATION_ERROR) and record-validation (VALIDATION_FAILED, duck-
typed on name === 'ValidationError') channels would each mis-file an
authored stack.

One classification row per new code in the runtime dispatcher error-code
vocabulary (door none, verdict boot-refusal), with the reachability
measurement re-taken on this tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno

* fix(runtime): strip tracker ids from the six new vocabulary rows' string prose

check:doc-authoring refuses an issue id inside sibling-package string
prose (a runtime string reaches authors who cannot resolve #NNNN); the
ADR anchor stays, the tracker ids move out of the strings. The comment
header above the rows keeps its id — comments are the reader who can.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno

* chore(changeset): grade the six new STACK_* codes minor; correct the 422 claim on METADATA_SCHEMA_INVALID

Clause-② is yes (six new error codes ship in spec's dist and cannot be
renamed once consumers branch on them), and a purely additive widening
of a published package's public surface takes at least minor — the
commit type may raise a bump but never lower it below what the act
requires (maintainer ruling 2026-09-04, decision batch objectstack-ai#35). Both
packages move from patch to minor; the changeset records why.

Review advisory A1: nothing in the tree assigns METADATA_SCHEMA_INVALID
a status — it stays the issues-carrying precedent, FLOW_INPUT_SCHEMA_INVALID
carries the 422 (flow-dispatch-status.ts), and the zod-shaped refusal
metadata-protocol stamps at 422 is INVALID_METADATA. A2: the base class
docblock states that issues is heterogeneous by design.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T6HeZvT9wdSJD1ZxJb5Eno

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 9, 2026
…and `wasm` leave the published config schema (ADR-0049 enforce-or-remove) (objectstack-ai#16376)

* wip(driver-turso): timeout bounds remote operations; localPath and wasm tombstoned (objectstack-ai#16024)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ

* docs(changeset): the spec ledger entry is an additive widening — minor, not patch (objectstack-ai#16024)

The Check Changeset step's WHICH LEVEL rule (maintainer, 2026-09-04, batch objectstack-ai#35):
a purely additive widening of a published package's public surface takes at
least minor, and the act sets the floor. The new D3 entry in
packages/spec/src/migrations/registry.ts is that act for @objectstack/spec.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ

* docs(driver-turso): the replica arm's sync is not cancelled, and the mirror does not declare `mode` (objectstack-ai#16024)

Two prose repairs the contract review raised as non-blocking; no behaviour and
no schema changes.

- packages/drivers/driver-turso/README.md — the README named both arms and the
  WebSocket gap but dropped the clause `turso-driver.ts`'s docblock and the
  changeset both carry: on the replica arm the native binding's own sync is not
  cancelled, only no longer awaited. Wording matched to the docblock.

- docs/design/driver-turso.md §10 — "Both declare exactly the keys the driver
  reads" overstated: the package-published mirror does not declare `mode`,
  which `TursoDriver.detectMode` reads. The sentence now claims only the
  direction ADR-0049 governs (no declared key the driver does not read) and
  names the gap. The gap itself is pre-existing and deliberately left alone.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 9, 2026
…the engine's stranded verdict (objectstack-ai#16587)

* feat(runtime, spec): the resume door's 400 FLOW_FAILED details carry the engine's stranded verdict

`POST /automation/:name/runs/:runId/resume` copied `errorMessage` and
`summary` off the engine result and dropped `status`, so
`AutomationResult.status: 'stranded'` (terminally failed but repairable by an
operator verb) reached the wire as the same 400 FLOW_FAILED a plain terminal
failure does. The objectstack-ai#16472 family ruling (option A): carry `status` and
`repairable` in the details of the existing code, no FLOW_STRANDED sibling.

- spec: `ResumeFailureDetailsSchema` (`@objectstack/spec/api`) declares the
  structure once — `{ runId, status?: 'failed' | 'stranded', repairable }`.
- runtime: the resume door forwards `status` verbatim when the engine stamped
  one, names the resumed run as `runId`, and answers `repairable` as
  `status === 'stranded'` — always present on this arm, present-and-false on
  the plain terminal exit. Trigger door and /actions unchanged.
- client: `automation.resume` docblock; docs: flows.mdx, client-sdk.mdx.
- pins: spec schema + type-level subset pin; runtime door pins (fake engine,
  every arm); verify wire pin through the real engine.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ

* chore(spec): regenerate the artifacts the new ResumeFailureDetailsSchema export moves

api-surface, export-origins, declaration-map, the generated api reference
page, and the unknown-key strictness ledger count (450 -> 451 in api/) —
each regenerated by `check:generated --fix`, only the five it proved stale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ

* docs(permissions): re-anchor the system-context census rows the resume-door helper moved

Pure line rot: the helper and its import shift four `ec.isSystem` read
sites in domains/automation.ts; rewritten by the gate's own --fix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ

* chore(changeset): grade @objectstack/client minor — a clause-② PR may not grade a package it grew as patch

Check Changeset's finding on objectstack-ai#16587: the PR declares clause-② yes and moved
packages/client/src/**, and the 2026-09-04 ruling (decision batch objectstack-ai#35, on
objectstack-ai#15294) binds per PR — at least `minor` for a package whose public surface
this PR moved, whatever the commit type says.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ

* fix(runtime): bind the relayed status to the published enum — a guard on the two terminal-failure members, and the /actions negative pin

Contract-review follow-ups on objectstack-ai#16587: resumeFailureDetails now returns
ResumeFailureDetails, relaying status through a guard on 'failed' |
'stranded' (satisfies-bound to the schema's enum) so the compile-time
binding is true by construction — still a relay, never a synthesised
verdict. actions-flow-dispatch-status.test.ts gains the exact-equality
negative pin the docblock claimed for /actions, and the docblock now names
both pin files.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ

* docs(permissions): re-anchor the census row the terminal-failure guard moved

Pure line rot again: the guard, its constant and their docblocks sit above
the anonymous-deny read in domains/automation.ts, shifting it :1057 -> :1079;
rewritten by the gate's own --fix, population unchanged (106 sites / 141 anchors).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 9, 2026
… record which intakes reach them (objectstack-ai#16564)

* lint: hand the registry's parsed tier the lowered stack so hook write rules reach handler-authored hooks

`os lint` judged the un-lowered normalized stack, so every rule in the
`hook-body-*` / `hook-api-update-readonly-*` family returned before reading a
hook authored as an inline `handler` function; `os build` lowers first and
never had the gap. `lintConfig` now runs `lowerCallables` on the same input
and hands the lowered view to the `parsed` tier only, leaving the
function-reading rules and the caller's stack untouched.

Doors measured with a control per leg (lint / build / validate), the two
validators carry the reach ledger in their headers, docs and changeset added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8

* lint: complete the hook-rule reach ledger — enumerate the intakes, pin the scaffold door

The reach annotation landed naming three `os *` commands. The doors are the
call sites of `runAuthoringRules`, and there are more of them than there are
commands: `os build` enters twice (the union run and the per-package run) and
`runScaffoldAuthoringRules` — which `os init` / `dev` drive over a rendered
template — is a fourth intake that lowers before it parses and has therefore
reached this family all along. A ledger that omits a reached door mis-states
coverage in the same direction the card is about.

Both rule headers now enumerate every measured intake, each recorded with the
body-authored control that makes its verdict readable, and the scaffold door
gains a pin with that control beside it. The pin asserts `schemaError` is null
before reading the verdict, so a stack that stops parsing cannot read as "no
finding". No rule logic changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8

* chore(changeset): grade @objectstack/lint minor under the clause-② declaration (objectstack-ai#16095)

The Check Changeset gate refuses patch on a package whose src moved under a
clause-② declaration (maintainer ruling 2026-09-04, batch objectstack-ai#35, objectstack-ai#15294); ruled
by the contract review on objectstack-ai#16095 (comment 5571527379).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XesLUWmuhjuRwmU618AZ1M

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 9, 2026
… succeed (objectstack-ai#16780)

* test(plugin-auth): make the MCP OAuth resource check falsifiable

The predecessor check asserted `opts.validAudiences` on the options object
captured from a mocked `oauthProvider`. The provider never consumes that
object, so the assertion was green whether or not the installed version read
the option -- and 1.7.2 does not read it at all. An assertion that cannot
fail is indistinguishable from one that passed.

Replace it with checks whose subject is what the REAL provider does:

- an option-surface liveness scan over the INSTALLED provider dist, carrying
  a two-way control so a 0-hit reading is a measurement rather than silence;
- an end-to-end block that boots a real authorization server from the exact
  options AuthManager produces and drives discovery -> DCR ->
  `authorize?resource=<mcp url>` -> consent -> token;
- a guard that the per-client resource check stays ON, so satisfying the
  flow by switching a security check off turns this red instead.

This commit is deliberately red: it is the reproduction.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

* fix(plugin-auth): register the MCP resource so RFC 8707 authorize can succeed

`@better-auth/oauth-provider` 1.7.2 resolves a requested `resource` from the
`oauthResource` table and, with `enforcePerClientResources` at its `true`
default, requires the client to be linked in `oauthClientResource`. Neither
row was ever written, so every MCP client that sends `resource=` was refused
at `/oauth2/authorize` with `invalid_target: requested resource <mcp url> is
not configured`. No token could be minted on 17.3.0.

Route (a): declare the resource rather than relax the check.

- `resources: [mcpResourceUrl]` seeds the sys_oauth_resource row from the
  provider's own `init`, idempotently and `insertOnly`, so an admin's later
  policy edits survive a restart.
- `clientRegistrationDefaultResources: [mcpResourceUrl]` links every newly
  registered client inside the DCR transaction -- the only place the link can
  happen, since a client registers anonymously about a second before login.
- `enforcePerClientResources` stays at its `true` default. A client with no
  link row is still refused, and a test asserts that.

Two dead options removed. Neither `validAudiences` nor `silenceWarnings`
occurs anywhere in the installed `@better-auth/oauth-provider` or
`better-auth` (0 hits each, against positive controls that fire), and the
`oauthAuthServerConfig` notice `silenceWarnings` claimed to suppress no
longer exists in 1.7.2 either. A field that is passed and read by nobody
looks like configuration and enforces nothing -- that is how this defect
survived a version bump, so the new option-surface liveness check refuses
any such field rather than allowlisting these two.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

* fix(plugin-auth): settle auth plugin init, and key the dev in-memory fallback by modelName

Registering the MCP resource made the oauth-provider seed a `sys_oauth_resource`
row from its plugin `init` — the first write this package ever performs during
better-auth construction. Two latent boot-path defects became reachable as soon
as it did, and both are fixed here:

* `betterAuth()` returns synchronously and runs plugin `init` behind
  `auth.$context`, so anything a plugin does at init was a promise nobody held.
  A failure escaped as an UNHANDLED REJECTION (fatal to the process by default)
  and, in tests, as a boot write racing its engine teardown. `createAuthInstance`
  now awaits `$context`, making the seed part of "the instance is ready" and a
  boot failure a rejection of the call that asked for it.

* The no-`dataEngine` fallback handed better-auth no `database` at all, which
  makes it build an in-memory store keyed by the schema KEY while every read
  resolves by `modelName`. Measured on better-auth 1.7.2: every renamed model —
  `user`/`sys_user` included, not just the oauth ones — answered
  "Model <name> not found" on that path. The fallback now builds the store
  itself, keyed the way the adapter reads it. Production is unaffected: it
  returns the ObjectQL adapter factory above this branch.

The pin that asserted `database === undefined` is replaced rather than edited —
it pinned exactly the branch this removes, and it read the value we passed
rather than what that value does. Its successor drives the factory and asks the
adapter for a renamed model.

Two suites had their measurement windows corrected, not their assertions
weakened: the sign-up refusal test now drains boot writes before arming its
insert recorder, and the membership-policy double stops filing every insert as
a membership regardless of which object it named.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

* test(plugin-auth): add the wrong-resource negative control; grade the changeset minor

Contract review of PR objectstack-ai#16780 returned CHANGES REQUIRED on two points.

F1 — the changeset level. The PR declares `Clause-②: yes` (the accept set of
`/oauth2/authorize` grows) while grading `@objectstack/plugin-auth` `patch`.
The maintainer's 2026-09-04 ruling (decision batch objectstack-ai#35, the WHICH LEVEL prose
in `pr-automation.yml`) settles the order between that and "a bug fix in a
released package takes `patch`": a purely additive widening of a published
package's public surface — "a new accepted key or value" — takes at least
`minor`, and the commit type never lowers the bump below what the act
requires. Graded `minor`.

F2 — the missing negative control. The body claimed "a request naming any
other resource is still refused exactly as before" and nothing tested it:
`authorizeWithResource`'s `resource` parameter was never varied. Without that
control a green suite cannot tell "the MCP resource is registered" from
"resource checking is off" — the same axis as this card's original defect,
where the assertion read the options we passed rather than what the provider
does.

The control is written as a DIFFERENTIAL against the real provider: one run,
one booted AS, one DCR client, one session, and two authorize requests that
differ only in `resource`. The registered MCP resource must reach consent; an
identifier that was never registered must answer `invalid_target`. That shape
reddens from both sides — remove the registration and the granted half fails,
seed the second resource and link clients to it and the refused half does —
where a bare refusal assertion would stay green under either. It asserts
nothing about the options object; the resource inventory it checks at the end
is read out of the AS's own store.

A second control covers the token leg: a code bound to the MCP resource at
authorize, redeemed with a `resource` the grant never carried, must be refused
`invalid_target` and mint nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37

---------

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 9, 2026
…wer, and their hook seams are guarded (objectstack-ai#16231) (objectstack-ai#16783)

* wip(engine): narrow findOne/update/delete result declarations — census measurement leg

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* feat(engine): declare findOne/update/delete result shapes and guard their hook seams

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* test(engine): pin the three declarations and repair the census consumers

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* chore(engine): changeset for the verb result declarations

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* chore(spec): regenerate the error-code ledger docs for the three new codes

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* fix(engine): keep the tracker id out of the update refusal's runtime prose

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* fix(engine): the seam refusal names the seam, not a culprit; repair the off-contract driver doubles

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* fix(changeset): grade the three repaired consumers `minor`, as the clause-② declaration requires

`Check Changeset`'s level axis is red on this PR: it declares `Clause-②: yes`
and grades three packages whose `src/**` the diff moves at `patch`. A purely
additive widening of a published package's public surface takes at least
`minor` (maintainer ruling 2026-09-04, decision batch objectstack-ai#35, on objectstack-ai#15294).

`@objectstack/metadata` and `@objectstack/metadata-protocol` are the two the
gate can name. `@objectstack/plugin-auth` rises for the same reason and is NOT
graded by the gate: `PUBLISHED_SOURCE_PATH` is anchored `^packages/([^/]+)/src/`
and this package's changed source is `packages/plugins/plugin-auth/src/` — one
directory level deeper, so it never enters the gate's "grown" set. That is the
blind spot carded as objectstack-ai#16713. The level floor comes from the act the PR declares,
not from what the instrument happens to measure.

No source, test or config byte moves; the level axis is the only thing this
commit answers for.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* test(spec): pin the three narrowed verb declarations, not only their seam guards

Ruling A has two halves — the declarations and the seam guards — and only the
guard half was pinned. Reverting `findOne` / `update` / `delete` to
`Promise<any>` while keeping the guards reddened nothing: every consumer repair
the census produced compiles identically against `any`, so those repairs record
that a narrowing once happened, not that it still holds. That is ADR-0049's
enforce-or-remove target.

Three `@ts-expect-error` cases under `check:test-typecheck` close it, on the
mechanism the neighbouring objectstack-ai#12248 block already relies on: each directive is
resolved by tsc today, so a widening back to `Promise<any>` leaves it UNUSED,
which is itself an error in a file whose debt ledger is exact.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* docs(changeset): state the runtime FROM/TO per door, and name both sources

The type FROM/TO was already per verb; the runtime half was one sentence for
three doors and named only the handler. The refusals' own `developerMessage`
names TWO sources — an `after*` handler that assigned an off-declaration value,
and a driver whose exit answered off `IDataDriver` — and the second one is the
source the seven test-double repairs in this PR actually came from, which is why
the refusal sentence names the seam instead of accusing the handler.

Three per-door lines now carry FROM (what the dispatch left, returned silently,
and who read it first) to TO (the registered 500 code raised at that seam).
Driver limbs cited are read off `packages/spec/src/contracts/data-driver.ts`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ADLdAs2pVcH17h9tZKWMBg

* chore(docs): regenerate the api reference from the merged tree

The merge of `origin/main` brought fourteen newly registered error codes into
`ERROR_CODE_LEDGER`; this branch adds three. Neither side's bytes can be text
merged into the other — both files are generated — so `os-regen-merge.sh` took
main's side in the merge commit and this commit re-derives them from the merged
source with `gen:schema && gen:docs`.

`contract.mdx`'s `Enum<... +N more>` counter reads `+325` = main's `+322` plus
this branch's three. `error-code-ledger.mdx` carries both sides' rows; all four
hook-result codes (the three added here and the `find()` sibling) are present.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018rzQyhLGC5iVs11V3TzRs5

---------

Co-authored-by: Claude <noreply@anthropic.com>
os-sam pushed a commit that referenced this pull request Sep 10, 2026
Graded `patch`: nothing is widened and no symbol is added. The contract already
published these shapes; the implementation is coming back to a declaration it had
already published. Checked against the recorded WHICH LEVEL ruling of 2026-09-04
(decision batch #35, on #15294), whose `minor` trigger is additive widening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XTBcV7zZHmokdyQgXjbyEU
os-bill pushed a commit that referenced this pull request Sep 11, 2026
…ged docs page (#15117)

Contract review returned FAIL with two must-fixes.

1. The changeset is `minor`, not `patch`. The written rule
   (`.github/workflows/pr-automation.yml`, maintainer ruling 2026-09-04 batch
   #35) is that a purely additive widening of a published package's public
   surface takes at least `minor`, and a commit type may raise a bump but never
   lower it below what the act requires. The PR's own `Clause-②: yes` line says
   this widens the accept set, in those words. The `find` precedent it leaned on
   does not reach: that was a NARROWING, it landed the day the rule was ruled,
   and the rule disclaims pre-rule `patch` precedents.

   How the wrong level survived local verification is the more useful half:
   `check-changeset-no-major` reads the clause-② declaration from the event
   payload and nothing else, so a local run without `--event` cannot exercise
   the level axis at all. Its exit 0 was recorded as a reading when the
   instrument could not have come back the other way.

2. `content/docs/ui/actions.mdx` — the page the repo's own Docs Drift Check
   flagged on this PR — is re-verified against the rewritten example. Its
   handler snippet still annotated `ctx: ActionContext`, a type that file no
   longer declares; it now imports and annotates the published
   `ActionHandlerContext`. The same snippet also wrote `completed_date`, which
   is `readonly` on `todo_task` and stamped by the object's `beforeUpdate` hook:
   copying it made the action refuse itself against `completed_date_required`.
   Both facts are verified against `task.object.ts` and `task.hook.ts`. The page
   now states the `delete` convention beside where it already states `find`'s.

Folded in: the example's comment attributed to the contract a request the
contract does not make. The contract asks for `ActionHandler`; it says so, and
says why a file of function declarations annotates `ActionHandlerContext`
instead. Comment-only — 11 changed lines, all comments or blank.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MkQhmuuJAVDjmeWNixwDDH
os-sales pushed a commit that referenced this pull request Sep 11, 2026
…ive clause ② sets (#17625)

`Check Changeset` failed the LEVEL AXIS on the previous head: a PR whose clause ②
is declared affirmative must grade at least one package whose `packages/**/src/**`
it moves at `minor` or above, and this changeset graded the only such package
`patch`.

The level is a mechanical floor, not an editorial reading of the act. The
maintainer ruling of 2026-09-04 (decision batch #35, on #15294) is written out
under "WHICH LEVEL" in the `Check Changeset` step: the commit type may raise a
bump but never lower it below what the act requires. The act here re-admits an
input class the merged tree refuses, on an authorisation surface, so the type
stays `fix(runtime)` and only the level moves. The changeset now records that
reasoning so a later reader does not re-grade it back down as a plain bug fix.

⛔ The declaration was not softened to fit the level, and neither the gate nor
the workflow was touched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TSf4DV7ziu4V5j73e46b7c
os-project-manager pushed a commit that referenced this pull request Sep 15, 2026
The diff adds MessagingService.registerChannelProvider to an already-published
class. A purely additive widening of a published surface takes at least minor
(maintainer ruling 2026-09-04, decision batch #35), and the PR declares clause
two yes, so patch was a self-contradiction inside one PR.

Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/xl tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants