Skip to content

docs(instructions): teach the logic brief the real ctx.api surface and the crm_ prefix - #1685

Draft
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-1678-logic-brief-real-data-surface
Draft

docs(instructions): teach the logic brief the real ctx.api surface and the crm_ prefix#1685
claude[bot] wants to merge 2 commits into
mainfrom
claude/issue-1678-logic-brief-real-data-surface

Conversation

@claude

@claude claude Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

Fixes #1678

.github/instructions/logic.md — the brief a backend agent reads before writing its first hook —
taught broker.find('opportunity', { filters }). That one line is wrong in three independent ways,
and it is the same example #855 already retired from AGENTS.md; it survived here because these six
briefs sat outside every gate in the repo until #1233 put them inside one.

What was wrong, re-measured on origin/main @ a9a9a301

The card's three greps were taken at 8c07f119; all three still hold on a fresh main:

Claim Command Reading
broker is not a thing here grep -rn '\bbroker\b' src/ | wc -l 0 (also 0 case-insensitively)
the briefs never write the prefix grep -rc 'crm_' .github/instructions/*.md six 0s
the brief teaches the plural key grep -n 'filters' .github/instructions/logic.md lines 18, 35

Two further readings taken for this PR, both against the pinned 17.3.0 packages:

  • @objectstack/runtime exports no Broker at all — the brief's import { Broker } from '@objectstack/runtime' never resolved to anything.
  • FILTER_OPERATORS is 16 operators, and $regex / $options are retired (refused with
    INVALID_FILTER / 400, prescribed replacement $icontains) — so the brief's four-row operator
    table was both incomplete and shaped like a different stack's AST ([['amount', '>', 50000]]).

What this PR changes

logic.md — rewritten against the real surface (Q1, ruled by the maintainer's #1518 Option A:
these briefs describe this workspace). Written from src/objects/_hook-api.ts as it stands after
PR #1675 settled HookQuery / HookCountQuery, plus the spec schemas — not from memory:

  • The surface: ctx.api, cast once per hook as HookApi and guarded; ctx.api.object(...)
    directly in an action body. Object names always written out with crm_.
  • The method table: find / findOne (where?, fields?, top?), count (where? and
    nothing else — the engine's legal keys are context, where), insert, update (the id travels
    inside the doc; there is no (id, doc) overload), delete. The three asymmetries a reader
    cannot guess are stated as such, including that top — not limit — is how this repo caps a read.
  • The predicate: where and only where; filter is a live alias but a compile error here and
    caught repo-wide by test/hook-query-predicate.test.ts; filters (plural) throws; findOne({})
    throws rather than returning an arbitrary row.
  • The operator table: all 16 measured operators with the where-document shape
    ({ field: { $op: value } }), the $and/$or/$not grouping, and the retired pair called out.
  • The hook shape: the eight camelCase events (beforeInsertafterFind) replace the
    before_create / after_create vocabulary, which this stack's enum rejects; the handler takes one
    ctx: HookContext (event, input, previous, user, api, id) rather than a broker, and the
    example is a real Hook literal with a default export.
  • The sandbox constraint, because it decides whether a first hook survives pnpm verify: a
    handler is lowered and runs body-only in QuickJS with no module scope, so its constants must be
    declared inside it — test/action-sandbox.test.ts runs that lowering pass over every hook.
  • The action shape: a named const … : Action re-exported from src/actions/index.ts, one
    action per export, type from the measured six, implementation in body under declared
    capabilities — not a bare exported arrow function.

metadata.md — the crm_ prefix only (Q2, ruled: fix it in the files that already name an
object, which today is these two). name: 'crm_contract', reference_to: 'crm_account', and one
Constraint Checklist entry so it does not drift back — including the point that the file stays
unprefixed while the name does not. No object names were invented in the four briefs that name
none: that would be adding requirements.

Q3 is not implemented, by ruling. #1233's guard is not extended to read identifiers — this repo
does not grow a gate farm (AGENTS.md:431, same disposition as #1262). The recurrence protection is
already live: that guard put these briefs inside the gate, which is why this was found at all.

Verification

pnpm verify fully green on 12e211d0 — all eight steps, run as validate, lint,
lint:i18n-gate, hygiene, hygiene:tokens (exit 0 each), then typecheck && build and test
under the shared heavy-verify lock (VERDICT command-exit 0 both times):

Test Files  161 passed (161)
     Tests  3402 passed | 1 skipped (3403)

Ablation — the #1233 guard really reads this file. Committed first, then mutated one
src/objects/_hook-api.ts mention to src/agents/ (deleted by #512), proved the mutation landed on
disk (grep -c 'src/agents/': 0 → 1, and no such directory exists), and ran the guard:

× .github/instructions/logic.md — the names-implies-exists case
  AssertionError: … advertises src/ directories that do not exist: agents
Test Files  1 failed (1)          # MUTATED_EXIT=1

Restored with git checkout HEAD -- PATH and proved it by bytes — git hash-object matches the
HEAD blob (d46fc53a…) and git diff HEAD is empty — then re-ran: 1 passed, 28 tests.

Both guard rules still hold by construction: logic.md names src/objects/ and src/actions/
inline (rule 1 does not go vacuous, and both exist), and neither file names any retired-layout path
(rule 2). .github/instructions/** is in the control-byte hygiene scan; scanned clean.

Landing

Draft, human floor. .github/instructions/** is AGENTS.md-class (2026-08-25 grading on
#1233): no auto-merge, not for the merge queue, no approval from an agent — the maintainer merges
this by hand. Same disposition as PR #1603 / #1677.

skip-changeset applied: nothing ships from this PR (the precedent for this directory is #1518).
.github/instructions/architect.md is untouched — PR #1677 owns it; src/objects/_hook-api.ts was
read but not touched, since #1676 is editing its comments.

Out of scope, filed separately

metadata.md's object-definition example teaches a shape the pinned spec rejects — reference_to
is an unrecognized key (FieldSchema prescribes reference), @objectstack/spec does not export
ObjectSchema at all (it is in @objectstack/spec/data, and the repo calls ObjectSchema.create()
with a named export, not export default {…} as ObjectSchema). Same defect class, different
question from the prefix this card rules on, and the reference_to half also appears in AGENTS.md,
so it needs its own card rather than a rider here.

CI triage, 2026-09-06 — what was red, why, and what fixed it

Red: Check Changeset, and nothing else. Read from the check-runs API for head 12e211d0 (the combined-status endpoint on this repo only ever returns Vercel, which is why this needed the other endpoint): Build and Test (22.x), Playwright, link-check, CodeQL, Analyze Code (javascript) and Label Pull Request were all success; Check Changeset appears twice — run 34027799455 failure, run 34027805740 skipped.

The failing job's log, verbatim:

##[error]This PR adds no changeset. Run 'pnpm changeset' and commit the generated .changeset/*.md file, or apply the 'skip-changeset' label if this change genuinely ships nothing (pure CI/docs chores).
##[error]Process completed with exit code 1.

Cause — a label race at PR-open, not a stale base. changeset-check.yml gates the job on if: !contains(github.event.pull_request.labels.*.name, 'skip-changeset'), and that is evaluated against the payload of the event that started the run:

Time (UTC) Event
10:34:28 PR opened
10:34:35 Check Changeset starts from the opened payload — no labels on it yet
10:34:36 skip-changeset applied — one second too late for the run already in flight
10:34:39 the labeled re-run fires and skips, correctly
10:34:43 the first run finishes failing

So this PR has carried that red since 15 seconds after it was opened — nothing turned red later. A workflow run is never re-evaluated, so the failed run stays bound to 12e211d0 for ever, and re-running it would only replay the same label-less payload.

The stale-base hypothesis was tested and killed as the cause. main had really moved 18 commits past this PR's base 8dccbbba, so the last green Build and Test measured a base that no longer exists — but the branch verified green both as it stood and after the merge, so the staleness was not the red. One of those 18 does touch the surface this brief documents: #1689 re-scoped the pin-claim comment in src/objects/_hook-api.ts from "measured on 17.2.0" to "first taken on 17.2.0, re-taken on the current pin 17.3.0". This brief already states 17.3.0, so the merge agrees with it. #1675 (count getting its own HookCountQuery) was already in this branch's history when it was cut, and the brief was written against it. Nothing was relaxed, skipped or deleted to reach green.

Fix: merge main into the branch (commit 8faec05c). That refreshes the base and gives the PR a head whose Check Changeset run is evaluated with skip-changeset already applied. No rebase, no amend, no force-push; the PR's own diff is unchanged — still .github/instructions/logic.md plus .github/instructions/metadata.md, and .github/instructions/architect.md remains untouched (PR #1677 owns it).

Gate re-run on 8faec05c, exit code captured before any pipe:

$ pnpm verify    # validate, typecheck, lint, lint:i18n-gate, hygiene, hygiene:tokens, build, test
 Test Files  161 passed (161)
      Tests  3410 passed | 1 skipped (3411)
PNPM VERIFY EXIT=0

pnpm test:coverage also exit 0 — All files 95.93 | 84.77 | 95.87 | 99.36. The E2E (Playwright) and link-check workflows are not part of pnpm verify; they re-run in CI on this head.

Posture unchanged: still a draft, no auto-merge, no merge queue, no approval. The maintainer merges it by hand.


Generated by Claude Code

`.github/instructions/logic.md` taught `broker.find('opportunity', { filters })`
— a data surface this repo does not have, in three separate ways at once:

  * `broker` has zero occurrences under `src/`, and `@objectstack/runtime`
    exports no `Broker` to import. Data access is ObjectQL through `ctx.api`.
  * `filters` (plural) is not a predicate key and not an alias: the engine
    rejects any option it does not recognise, so it throws on the first call.
    The canonical key is `where`.
  * `'opportunity'` names an object that does not exist — there is no runtime
    prefix injection, so every business object name is written out as `crm_*`.

It is the same example #855 already retired from AGENTS.md; it survived in the
per-role briefs because they sat outside every gate until #1233 put them inside
one. Rewritten against the pinned 17.3.0 packages and `src/objects/_hook-api.ts`
rather than from memory: the method/option table (`count` narrower than `find`,
`top` not `limit`, `update` with the id inside the doc), the measured
`FILTER_OPERATORS` table with the retired `$regex`/`$options` called out, the
eight camelCase hook events, the real `Hook` / `HookContext` handler shape, the
sandbox's no-module-scope constraint, and the named-export `Action` shape.

`metadata.md` gets the `crm_` prefix half only: `name: 'crm_contract'`,
`reference_to: 'crm_account'`, and a Constraint Checklist entry so it does not
drift back. No object names were invented in the four briefs that name none.

Co-authored-by: Claude
@vercel

vercel Bot commented Sep 6, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 6, 2026 2:45pm UTC

Request Review

Two reasons, neither of which changes this PR's own scope.

1. The head commit carries a permanently-failed `Check Changeset` run. That
   run started 7s after the PR was opened; the `skip-changeset` label landed
   1s later, but the job had already read a payload without it, so the
   workflow's `if: !contains(labels, 'skip-changeset')` guard let the job run
   and it failed 8s after starting. The `labeled` re-run skipped correctly,
   but the failed run stays attached to that head for ever and no event
   re-evaluates it. A new head is the only way to clear it without a rebase
   or a force-push.

2. The base had moved 18 commits since the PR was opened, so the last green
   `Build and Test` measured a base that no longer exists.

One of those 18 touches the surface this brief documents: #1689 re-scoped the
pin-claim comment in `src/objects/_hook-api.ts` from "measured on 17.2.0" to
"first taken on 17.2.0, re-taken on the current pin 17.3.0". The brief already
states 17.3.0, so the merge agrees with it rather than contradicting it. #1675,
which gave `count` its own `HookCountQuery`, was already in this branch's
history when it was cut and the brief was written against it.

The diff against main is unchanged: `.github/instructions/logic.md` plus
`.github/instructions/metadata.md`.

Co-authored-by: Claude
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline skip-changeset PR ships nothing to users (pure CI/docs chore) — changeset gate waived

Projects

None yet

1 participant