Repository navigation
feat: move templates and reference app onto the fluent DSL - #15
Conversation
Every generated primitive now reaches its projections through itself. - `t` comes from the primitive package — action/query/jobs — never from @ultimat3/schema; the unused schema dep leaves the generated package.json - generated contract tests drive `.as()`, `.contract()`, `.tool()`, `.openapi()`, `.enqueue()`, `$view`/`$describe`/`$parse` — no test reaches a projection function or `.def` - the denial they pin is the interesting one: an actor holding the grant in the wrong org, denied by the predicate before the handler runs - `x g policy` declares its permission set both ways — a `declare module` augmentation so a typo is a build error, and `definePermissions()` so the same set holds at runtime - `toDenyPolicy` decides a real `Policy` through `run()`. It only knew `evaluate()`, which no Policy has, so it answered "not a policy" for every policy the framework builds — a matcher that could never fail on an allow - `x g job` pins idempotency through a memory driver instead of `toEmitSteps`, which needed a database the generated test does not have - generated output is Biome-clean: import order, line width, package.json Proof: `x new` + all 9 generators written to a sandbox → tsc green, biome green, and its own suite 114 pass / 0 fail on the first run. Co-Authored-By: Claude <noreply@anthropic.com>
Every primitive declaration in examples/dummy now matches the shapes the generators emit and the framework actually accepts: - `t` from the primitive package (action/query/jobs), never a second import - policies are named objects reused verbatim by action, query and MCP — no rule is re-declared inline; `definePermissions` + a `PermissionRegistry` augmentation make a typo a build error, and `defineRoles` grants them - tenancy travels in `input` because a predicate is synchronous; a row-level rule reads `row`, which the surface loaded - queries return a real `SqlSource` via `from()`, ordered and bounded - jobs/tasks enqueue through their own handle; `concurrency` is the job's cap - mail uses `defineMail`'s block templates instead of a shape that never existed - `app.config.ts` is the real `defineConfig` surface; `api/index.ts` registers whole modules, so the export name is the primitive's name Fixes a framework defect the app exposed: augmenting `CtxServices` broke `createContext`, and `ctx.posts` typed as a service but read `undefined` at runtime. Services now ride on the context itself, framework fields winning on collision. `Infer` joins `t` in each primitive package's re-export. docs/idea/02-primitives.md updated so the spec's examples compile. Reference-app typecheck 432 -> 320 errors, every remaining one owned by the data substrate (PR 5) or the missing test fixtures; `bun test` 1341 pass / 17 fail, 0 framework failures. Co-Authored-By: Claude <noreply@anthropic.com>
- new admin.ts template: --admin emits the per-entity AdminResourceOptions
override, wired into defineAdmin's resources map by hand (one line, same
shape as packages/db/src/schema.ts's re-export)
- resource generator now splits ui.tsx into card/form components, ships a
feature i18n catalog (empty/updated/titleLabel/submit keys) across every
--locales entry (default en), and route/live catalogs follow the same list
- x g regenerates x.manifest.json after every write, via the same scanApp
the manifest command uses, so the facts table never lags the resource
- docs/architecture/12-generated-app.md rewritten to describe what the
generator actually emits: no fictional second Drizzle schema file, no
duplicate MCP tool path — mcp: { expose: true } + include: 'exposed'
already covers it
Co-Authored-By: Claude <noreply@anthropic.com>
- new examples/dummy/type-chain.test.ts renames `excerpt`→`summary` in the real posts schema, typechecks the app before/after with the real compiler, and pins the exact diff — proving the rename is caught, not a raw error count - ties `PostView` to the `posts` entity's own columns via `satisfies Record<PostViewKeys, unknown>` (hop 4, docs/architecture/05-type-chain.md) so a renamed/dropped column fails to compile at the view declaration itself, instead of silently drifting - scope is honest: hops 5+ run through packages/db's repo/query layer, which PR 5 (data substrate) has not built yet, so they are out of this proof Co-Authored-By: Claude <noreply@anthropic.com>
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 24 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (35)
📝 WalkthroughWalkthroughThe change updates framework contracts, the dummy application, CLI generation, boundary checks, scheduling, mail flows, and documentation. It adds typed authorization, scoped repositories, locale-aware resources, admin output, manifest writing, and stronger validation. ChangesFramework contracts and authorization
Dummy application
CLI generator
Documentation and validation
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
Sequence Diagram(s)sequenceDiagram
participant CLI
participant ResourceGenerator
participant FileWriter
participant Manifest
CLI->>ResourceGenerator: resolve locales and admin options
ResourceGenerator-->>CLI: return feature files and catalogs
CLI->>FileWriter: validate paths and write files
FileWriter-->>CLI: report successful writes
CLI->>Manifest: scan application and write x.manifest.json
Manifest-->>CLI: return build ID
``
</details>
<!-- walkthrough_end -->
<!-- pre_merge_checks_walkthrough_start -->
<details>
<summary>🚥 Pre-merge checks | ✅ 4 | ❌ 1</summary>
### ❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
| :----------------: | :--------- | :------------------------------------------------------------------------------------ | :--------------------------------------------------------------------------------- |
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 51.85% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
<details>
<summary>✅ Passed checks (4 passed)</summary>
| Check name | Status | Explanation |
| :------------------------: | :------- | :------------------------------------------------------------------------------------------------------- |
| Description Check | ✅ Passed | Check skipped - CodeRabbit’s high-level summary is enabled. |
| Title check | ✅ Passed | The title clearly summarizes the main change: moving generators and the reference app to the fluent DSL. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
</details>
</details>
<!-- pre_merge_checks_walkthrough_end -->
<!-- finishing_touch_checkbox_start -->
<details>
<summary>✨ Finishing Touches 💡 1</summary>
<!-- finishing_touch_suggestion:docstrings -->
<details>
<summary>📝 Generate docstrings 💡</summary>
- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> Create stacked PR
- [ ] <!-- {"checkboxId": "3e1879ae-f29b-4d0d-8e06-d12b7ba33d98"} --> Commit on current branch
</details>
<details>
<summary>🧪 Generate unit tests (beta)</summary>
- [ ] <!-- {"checkboxId": "f47ac10b-58cc-4372-a567-0e02b2c3d479", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Create PR with unit tests
- [ ] <!-- {"checkboxId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8", "radioGroupId": "utg-output-choice-group-unknown_comment_id"} --> Commit unit tests in branch `feat/generators-fluent-dsl`
</details>
</details>
<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->
---
<sub>Comment `@coderabbitai help` to get the list of available commands.</sub>
<!-- tips_end -->
|
There was a problem hiding this comment.
Actionable comments posted: 37
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/cli/src/templates/action.ts (1)
31-31: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftRoute generated MCP descriptions through localization. The action, mutator, and query templates each emit raw English text into a user-facing MCP surface. Define a localized description path once and project it to each generated declaration.
packages/cli/src/templates/action.ts#L31-L31: replace the generated action description literal with the localized description path.packages/cli/src/templates/action.ts#L61-L61: replace the generated mutator description literal with the localized description path.packages/cli/src/templates/query.ts#L30-L31: replace the generated query description literal with the localized description path.As per coding guidelines, “Do not hardcode user-facing strings; route all user-facing text through
t().”🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/templates/action.ts` at line 31, Replace the hardcoded MCP descriptions with the shared localized description path, using the existing t() localization mechanism. Update the generated action and mutator declarations in packages/cli/src/templates/action.ts at lines 31-31 and 61-61, and the generated query declaration in packages/cli/src/templates/query.ts at lines 30-31; ensure all three user-facing descriptions resolve through localization.Sources: Coding guidelines, Path instructions
packages/cli/src/templates/query.ts (1)
27-38: 🚀 Performance & Scalability | 🟠 Major | ⚡ Quick winBound
limitin the generated input schema.
packages/cli/src/templates/query.tsemitst.number.default(50), so fractional, negative, and arbitrarily large finite values reach.limit(limit). Generatet.number.int().min(1).max(50).default(50)to satisfy the bounded live-query rule indocs/idea/03-realtime.md.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/cli/src/templates/query.ts` around lines 27 - 38, Update the generated input schema in the query template’s `input` definition to use an integer limit bounded from 1 through 50, while retaining the default of 50. Keep the existing `sql` limit flow unchanged so it receives the validated value.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/architecture/12-generated-app.md`:
- Line 118: Update the command example near the generated-app documentation
example to satisfy markdownlint MD014 by removing the leading shell prompt
character from the command, or by adding representative output if the prompt is
retained.
- Around line 128-142: Align the canonical generated-feature layout and
registration documentation with the documented flat files: use actions.ts,
live.ts, jobs.ts, and ui.ts instead of nested action/live/job paths and
ui.tsx/ui/. Update the related registration guidance alongside the inventory so
both sections describe the same generated structure.
- Around line 141-145: Update the generated test scaffolds documented in the
“*.test.ts” row and the following verification statement so generators emit
passing tests with a real default assertion instead of intentionally failing
placeholders. Ensure the generated source and test output satisfy the existing
packages/cli generator contract and keep bun run verify green.
- Around line 147-151: Update the generated entity/resource registration flow
described in the architecture documentation so authors no longer hand-edit
packages/db/src/schema.ts or apps/admin/src/index.ts for each resource. Derive
both registrations from the canonical generated declaration, or have the
generator update them deterministically and idempotently, while preserving the
existing defineAdmin({ entities, resources }) behavior.
In `@docs/idea/02-primitives.md`:
- Around line 24-25: Update the publishPost example call to include the required
orgId input alongside postId, matching the schema defined in the publishPost
action and preserving the existing notify default.
- Line 29: Update the mcp.description value in the documented configuration to
use the supported catalog-backed localization mechanism instead of the hardcoded
“Publish a draft post” text. Keep the exposed MCP description behavior unchanged
while ensuring generated actions receive localized text through mcp.description.
- Around line 114-117: Update the local operation associated with postLike so
replaying the same input cannot increment likeCount more than once; use an
idempotent update or mutation identity, and align the server-side operation with
the same like/unlike semantics rather than treating toggleLike as like-only.
- Around line 141-143: Update the query in the sql mapping around repo.feedPage
to make its ordering total by appending the posts primary key as the final
descending sort key after createdAt. Keep the existing orgId filter and limit
unchanged.
- Around line 142-143: Update the from<PostSummary> example’s repo.feedPage call
to convert orgId with the existing toOrgId helper before passing it to feedPage,
matching the branded OrgId usage in the live example.
- Around line 30-32: Update the documentation around handle to avoid presenting
publishPost or publishPost.job() as a JobHandle: retain
notifySubscribers.enqueue({ postId: post.id }) for the declared job, and replace
artifact row 4 with the actual job declaration and payload, or document the
adapter that makes the action compatible with JobHandle.
- Around line 90-92: Update postPublish and its call path so authorization
always has the post row needed for authorship validation: load the tenant-scoped
post before evaluating the guard, or enforce ownsPost (with the existing admin
allowance) inside ctx.posts.publish(). Remove the row === null bypass while
preserving organization and permission checks.
In `@examples/dummy/app.config.ts`:
- Around line 24-28: Review the relationship between the database poolSize and
jobs concurrency in the app configuration, confirming whether web requests and
Postgres job workers share the same pool. If they do, increase database.poolSize
to leave sufficient connections for HTTP handling or configure the worker with a
separate pool; preserve the existing jobs concurrency otherwise.
- Around line 23-35: Update examples/dummy/app.config.ts:23-35 to declare
APP_URL and BUILD_ID alongside the existing *Env keys, and ensure defineConfig
validates every declared environment key at boot with a stable named error,
cause, and runnable fix; restore local validation here if defineConfig does not
provide it. Update examples/dummy/apps/web/shared/client.ts:15-16 to consume the
resolved APP_URL and BUILD_ID values from config instead of process.env, remove
the empty-string baseUrl fallback, and preserve BUILD_ID validation so
X_CONTRACT_DRIFT remains enforced.
In `@examples/dummy/apps/web/api/index.ts`:
- Around line 22-36: Ensure the server boot path performs a value import of the
API module containing the registerActions and registerQueries calls, rather than
importing only Api as a type from shared/client.ts. Preserve the existing
registration calls and type shape; the runtime import must execute them before
actions or queries are used.
In `@examples/dummy/apps/web/api/tasks.ts`:
- Around line 16-24: Update the task API and scheduler invocation so
TaskDefinition.enqueue receives the scheduled occurrence instant, then change
nightlyDigest’s enqueue callback to derive runDate from that argument via
localDateIn instead of systemClock.now(). Propagate the occurrence through all
enqueue call sites and preserve the existing occurrence-scoped deduplication
behavior.
In `@examples/dummy/apps/web/app/digest/jobs.ts`:
- Around line 59-62: Update deliverDigest to accept the validated scheduled
epoch-millisecond value batch.at and use it as the after argument when calling
nextDigestAt instead of ctx.now(). Derive the load-posts boundary from that
scheduled digest instant while preserving the existing timezone and one-day
window behavior.
In `@examples/dummy/apps/web/app/posts/actions.contract.test.ts`:
- Line 10: Remove the runtime api bootstrap import from actions.contract.test.ts
and relocate the required API registration to shared test setup outside
apps/web/app/. Ensure this test still receives the setup through the test runner
configuration while keeping the app test free of runtime api imports.
- Around line 92-95: Update the test for every post action in the contract test,
including the missing createPost case, so each action receives otherwise valid
input with exactly one invalid postId or orgId. Change the createComment fixture
to use a valid non-empty body, ensuring each assertion specifically exercises
UUID validation rather than another field.
- Around line 42-44: Add a regression case in the publishPost contract tests
using a post belonging to organization B while invoking publishPost with
organization A’s orgId and an appropriate actor. Assert that the call rejects
with X_POLICY_DENIED, reusing the existing postsService/publishPost test setup
and preserving the current same-organization coverage.
In `@examples/dummy/apps/web/app/posts/actions.ts`:
- Line 33: Route every user-facing mcp.description through the supported t()
localization path: update the publish and comment actions in
examples/dummy/apps/web/app/posts/actions.ts lines 33-33 and 48-48, the post,
public post, and tenant-scoped slug queries in
examples/dummy/apps/web/app/posts/live.ts lines 41-41, 59-59, and 84-84, and the
like mutator in examples/dummy/apps/web/app/posts/mutator.ts line 33-33. Add or
reuse the appropriate translation keys while preserving each description’s
meaning, and remove the hardcoded literals.
- Around line 34-38: Scope toggleLike through postsService.like by loading the
target post with byId() using ctx.actor.orgId before calling insertLike(),
rejecting cross-organization post IDs at the service/repository boundary. The
sites examples/dummy/apps/web/app/posts/actions.ts lines 34-38 and 49-50 and
examples/dummy/apps/web/app/posts/mutator.ts lines 34-38 require no direct
changes; they are corrected by the scoped service lookup.
In `@examples/dummy/apps/web/app/posts/live.live.test.ts`:
- Line 34: Update the toggleLike invocation in the live test to include the
required orgId argument, using acme.id, alongside postId so it matches the
mutator contract.
In `@examples/dummy/apps/web/app/posts/policy.ts`:
- Around line 69-73: The postPublish policy currently allows authorization when
row is null, bypassing authorship checks for pre-handler evaluation. Update
postPublish to fail closed for null rows, or ensure the authorization flow loads
the PostRow before evaluating the policy, while preserving the same-org and
ownsPost checks; add a regression test proving another member cannot publish
someone else’s draft.
In `@examples/dummy/apps/web/app/posts/repo.ts`:
- Around line 100-102: Align the post schema and public lookup so published
slugs are globally unique: update the uniqueness constraint in posts.ts to
enforce slug uniqueness across published posts rather than only orgId plus slug,
while preserving the publishedBySlug slug-only lookup and its existing result
behavior.
In `@examples/dummy/apps/web/shared/services.ts`:
- Around line 24-25: Add a boundary validation in scripts/boundaries.ts for
examples/dummy/apps/web/shared/, scanning all import declarations and rejecting
any shared/ to app/ dependency, including import type statements that
Bun.Transpiler.scanImports omits. Integrate this check into the existing x
verify boundary flow and preserve the shared directory as a leaf.
In `@examples/dummy/packages/db/src/tags.ts`:
- Around line 21-32: Update the cache tag declarations near CacheTagRegistry to
define and consume TAG_CASCADE, mapping post and like to the feed and blog
targets so invalidation propagates; alternatively remove the unused cascade map.
Type the map as Partial<Record<TagName, readonly TagName[]>>, preserve the
explicit CacheTagRegistry members, and do not replace them with an extends
Record<TagName, true> projection.
In `@examples/dummy/type-chain.test.ts`:
- Around line 45-67: The compiler diagnostic collection around Bun.spawn must
reject nonzero proc.exited results instead of returning zero diagnostics when
tsc emits unparseable configuration errors. Route this failure through the
existing UltimateError subclass, using an appropriate X_* code and a runnable
fix instruction; retain normal parsing only for successful compiler execution.
- Around line 121-125: Update the test setup in type-chain.test.ts to import
beforeAll and afterAll from bun:test, capture the original schema content once
in beforeAll, and compare the final schema content exactly in afterAll. Remove
the current content assertions from “the schema file is restored exactly, pass
or fail” so the restoration check is order-independent and also works when that
test is filtered or skipped.
- Around line 88-94: Update the test around typecheckApp so it never writes to
the tracked schema fixture, including on interruption or hard exit. Run the
rename/typecheck against an isolated temporary schema copy and point the test’s
typecheck configuration to that copy; remove the concurrent-suite claim,
preserving comparison and cleanup behavior for normal execution.
- Around line 24-26: Update the compiler path setup in type-chain.test.ts to
resolve the TypeScript binary from examples/dummy’s local dependencies, matching
the bunx tsc compiler used by x verify, instead of the repository-root
node_modules path. Keep the existing ROOT and SCHEMA_FILE resolution unchanged.
In `@packages/cli/src/cmd-generate.ts`:
- Around line 180-192: Validate locale segments before passing them to generate,
rejecting traversal or path-separator values with X_SCAFFOLD_PATH_ESCAPE. Update
writeFiles() to resolve each GeneratedFile.path beneath the app root and reject
any path escaping that root with the same error, before writing files.
In `@packages/cli/src/templates/resource.ts`:
- Line 190: Update generate() and the routeFiles call to preserve the requested
surface from target.surfaceDir instead of hardcoding surface: 'app'; propagate
the selected surface through ResourceOptions, or validate and reject site
resources before any files are written.
- Around line 158-172: Centralize locale defaults, validation, normalization,
and deduplication in the shared locale resolver, then update resourceFiles in
packages/cli/src/templates/resource.ts (lines 158-172) to use it instead of
DEFAULT_LOCALES; update the corresponding locale fallback in
packages/cli/src/templates/route.ts (lines 116-136) to use the same resolver.
Remove or stop using duplicated local fallback logic while preserving the
resolver’s normalized locale output at both sites.
- Around line 150-155: Update catalogSource to emit the admin title entry
matching the titleKey generated by adminFiles(), using the
admin.${feature.kebab}.title key and an appropriate feature title value while
preserving the existing catalog entries.
In `@packages/cli/src/templates/scaffold-repo.ts`:
- Line 241: Update the generated seed script output in scaffold-repo to use
Bun.stdout.write instead of process.stdout.write, preserving the JSON payload
and trailing newline exactly.
In `@packages/core/src/context.ts`:
- Around line 87-90: Make the comment match the actual behavior of the context
object: remove the claim that direct service access throws X_SERVICE_MISSING,
while retaining the existing useService() guarantee. Update only the explanatory
comment near the services spread and preserve the frozen plain-object
implementation.
In `@packages/testing/src/matchers.test.ts`:
- Around line 57-59: Update decide to return false for non-policy input and
remove both type guards, so junk is distinguished from an allowed policy.
Strengthen the toDenyPolicy test to assert the diagnostic message produced by
matchers.ts at the referenced location, rather than relying on .not polarity; if
the matcher is async-only, assert the rejected promise throws the expected run()
message.
---
Outside diff comments:
In `@packages/cli/src/templates/action.ts`:
- Line 31: Replace the hardcoded MCP descriptions with the shared localized
description path, using the existing t() localization mechanism. Update the
generated action and mutator declarations in
packages/cli/src/templates/action.ts at lines 31-31 and 61-61, and the generated
query declaration in packages/cli/src/templates/query.ts at lines 30-31; ensure
all three user-facing descriptions resolve through localization.
In `@packages/cli/src/templates/query.ts`:
- Around line 27-38: Update the generated input schema in the query template’s
`input` definition to use an integer limit bounded from 1 through 50, while
retaining the default of 50. Keep the existing `sql` limit flow unchanged so it
receives the validated value.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro
Run ID: f6970b52-12fa-46e0-9500-8304c2c5774e
📒 Files selected for processing (55)
docs/architecture/12-generated-app.mddocs/idea/02-primitives.mdexamples/dummy/app.config.tsexamples/dummy/apps/web/api/index.tsexamples/dummy/apps/web/api/tasks.tsexamples/dummy/apps/web/app/digest/jobs.tsexamples/dummy/apps/web/app/digest/mail.tsexamples/dummy/apps/web/app/orgs/actions.tsexamples/dummy/apps/web/app/orgs/entity.tsexamples/dummy/apps/web/app/orgs/jobs.job.test.tsexamples/dummy/apps/web/app/orgs/jobs.tsexamples/dummy/apps/web/app/orgs/mail.tsexamples/dummy/apps/web/app/orgs/policy.tsexamples/dummy/apps/web/app/posts/actions.contract.test.tsexamples/dummy/apps/web/app/posts/actions.tsexamples/dummy/apps/web/app/posts/entity.tsexamples/dummy/apps/web/app/posts/jobs.tsexamples/dummy/apps/web/app/posts/live.live.test.tsexamples/dummy/apps/web/app/posts/live.tsexamples/dummy/apps/web/app/posts/mail.tsexamples/dummy/apps/web/app/posts/mutator.tsexamples/dummy/apps/web/app/posts/policy.tsexamples/dummy/apps/web/app/posts/repo.tsexamples/dummy/apps/web/app/settings-actions.tsexamples/dummy/apps/web/shared/client.tsexamples/dummy/apps/web/shared/policies.tsexamples/dummy/apps/web/shared/services.tsexamples/dummy/packages/core/src/index.tsexamples/dummy/packages/core/src/membership.test.tsexamples/dummy/packages/core/src/membership.tsexamples/dummy/packages/db/src/tags.tsexamples/dummy/packages/domain/src/index.tsexamples/dummy/packages/domain/src/roles.tsexamples/dummy/type-chain.test.tspackages/action/src/index.tspackages/cli/src/cmd-generate.test.tspackages/cli/src/cmd-generate.tspackages/cli/src/templates/action.tspackages/cli/src/templates/admin.tspackages/cli/src/templates/entity.tspackages/cli/src/templates/index.tspackages/cli/src/templates/job.tspackages/cli/src/templates/policy.tspackages/cli/src/templates/query.tspackages/cli/src/templates/resource.tspackages/cli/src/templates/route.tspackages/cli/src/templates/scaffold-app.tspackages/cli/src/templates/scaffold-repo.tspackages/core/src/context.test.tspackages/core/src/context.tspackages/entity/src/index.tspackages/jobs/src/index.tspackages/query/src/index.tspackages/testing/src/matchers.test.tspackages/testing/src/matchers.ts
Security
- action() gains an optional `row:` loader, awaited once per invocation
before the guard, so a row-level rule reaches its predicate with the row
the surface loaded. Predicates stay synchronous.
- postPublish now fails CLOSED (`row !== null && ownsPost`). It previously
allowed any same-org actor holding `post:publish` to publish another
member's draft, because the action guard supplied no row.
- postsService.like()/unlike() load the post through the tenant-scoped
byId() first — a foreign postId could insert a cross-tenant likes row.
- Post slugs are globally unique: /blog/{slug} carries no tenant, so
per-org uniqueness let two orgs claim one public URL.
Correctness
- TaskDefinition.enqueue receives the scheduled occurrence, so a late or
caught-up tick no longer derives its payload from the worker's clock.
- deliverDigest takes the slot instant; the 24h window ends at the slot,
not at execution time.
- The mutator's local twin is convergent (likedByMe), not an increment.
- Live/bounded reads append the primary key: ORDER BY createdAt alone is
a partial order. Same fix in the query generator, whose test now pins it.
- publishPost invalidates tag.blog — the ISR blog pages never revalidated.
CLI
- x g enforces generated-path containment (X_SCAFFOLD_PATH_ESCAPE) for
--locales and for every written file.
- One locale resolver owns defaults, validation, normalization, dedupe.
- x g resource emits the admin title key; rejects --surface site.
- Generated seed script uses Bun.stdout.
Tests + docs
- app/ no longer imports api/ at runtime; the boot moved to the preload.
- Contract tests exercise uuid validation per action, plus cross-org cases.
- toDenyPolicy's "not a policy" test asserts the message, not .not polarity.
- type-chain: harness faults name themselves, exact restore in afterAll,
signal-safe fixture restoration.
- toMatchError never existed; 6 call sites and 5 docs now use
toBeUltimateError.
- x verify runs the shared/ leaf boundary rule.
- createContext's comment no longer promises X_SERVICE_MISSING on direct
service access.
Co-Authored-By: Claude <noreply@anthropic.com>
- 12-generated-app.md: slice layout now lists `queries/` and `tasks/`, the two generator outputs the canonical inventory omitted - one way to enqueue everywhere: `<job>.enqueue(input)` replaces `ctx.jobs.enqueue(job, input)` in every example across docs, wiki and package READMEs. Both resolve the same ambient facade; two call forms for one call is the drift axiom 1 rejects - artifact row 4 stops implying an action is a `JobHandle`. `publishPost.job()` returns `ActionJobHandle` — no `.enqueue`, no `queue`/`retry`/`parse` — so it is registered, never enqueued - pin why `mcp.description` stays outside `t()`: it is the OpenAPI operation `summary`, and `buildOpenApi`'s bytes are what `x verify` diffs. The ambient translator is request-scoped, so routing it through `t()` makes a contract artifact locale-dependent Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
examples/dummy/apps/web/app/digest/jobs.ts (2)
56-80: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftBound the digest query to the scheduled local-day interval.
publishedSince(orgId, since)has no upper bound. A delayed job can include posts published afterslotAt. The fixed86_400_000offset also selects the wrong local-day boundary across DST changes.Change the service and repository contract to query a
[start, end)interval. Derive both boundaries fromslotAtandmember.tz.As per path instructions, queries must be bounded.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/dummy/apps/web/app/digest/jobs.ts` around lines 56 - 80, Update the digest job’s load-posts flow and the underlying service/repository contract to query a bounded [start, end) interval instead of using publishedSince(orgId, since). Derive the local-day start and exclusive end from input.slotAt and member.tz, preserving the scheduled local date across DST transitions, then pass both boundaries through the relevant post-query symbols.Source: Path instructions
15-46: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy liftPreserve the scheduled occurrence through
sendDigest.
nightlyDigestsupplies onlyrunDate, butsendDigestderives member slots fromctx.now(). If the parent job starts late,scheduleByZone()creates slots for the worker date instead of the scheduled occurrence. The laterslotAtthen preserves the wrong digest.Add the occurrence epoch milliseconds to
sendDigestinput. Use that value forscheduleByZone()and retain it throughdeliverDigest.As per path instructions, jobs must carry occurrence-specific resume-time data in their payload.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@examples/dummy/apps/web/app/digest/jobs.ts` around lines 15 - 46, Update sendDigest’s input and scheduling flow to carry the scheduled occurrence epoch milliseconds in the payload, rather than deriving it from ctx.now(). Pass this occurrence value to scheduleByZone(), and propagate the same value through deliverDigest’s slotAt so retries preserve the original digest occurrence.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/architecture/12-generated-app.md`:
- Line 177: Update the architecture example around packages/db/src/schema.ts so
packages/db no longer re-exports the entity from `@myapp/web/app/post/entity`.
Move the canonical post entity into a lower-tier database schema module, then
have the application and admin layers import that shared entity while preserving
migration registration and drift-hash coverage.
In `@docs/idea/02-primitives.md`:
- Around line 134-140: The like-only operation should not be exposed as a
toggle. Rename the operation and its related local/server references to
likePost, preserving its convergent set-to-true behavior, or add a desired-state
parameter and implement matching like/unlike insert/delete behavior in both
local and server.
In `@examples/dummy/apps/web/api/index.ts`:
- Around line 13-18: Ensure runtime server boot imports the API module for
side-effect registration, so registerActions(...) and registerQueries(...)
execute in the built server path. Update the server entry’s boot flow or
equivalent module-scan path, using the existing API module and preserving the
framework’s discovery behavior for non-runtime tooling.
In `@examples/dummy/apps/web/app/posts/`[id].tsx:
- Around line 65-75: Update the native comment form’s createComment submission
to include orgId from props.data.orgId alongside postId and body, ensuring
submissions before hydration satisfy the updated createComment schema.
In `@examples/dummy/apps/web/shared/services.ts`:
- Line 26: Remove the PostRow import from shared/services.ts so the shared leaf
no longer depends on app/posts/policy.ts. Move the required policy row shape to
a lower-tier contract, or define only the narrow authorship result needed by
shared/services.ts within shared/, while preserving the existing service
behavior.
In `@examples/dummy/type-chain.test.ts`:
- Around line 165-173: Update the test to import process from node:process
instead of relying on the global process, and retain or add the required comment
explaining that synchronous signal cleanup needs process.on, process.off, and
process.exit. Use the imported process throughout the signal-listener and
restore logic.
In `@packages/cli/src/cmd-generate.test.ts`:
- Around line 11-19: Create one shared thrownBy test helper reachable by both
test files, using UltimateError with a stable X_* code, cause, and executable
fix instruction for the no-throw failure path; delete the duplicate local
definitions and import the shared helper in
packages/cli/src/cmd-generate.test.ts lines 11-19 and
packages/cli/src/templates/locales.test.ts lines 4-12.
In `@packages/cli/src/cmd-generate.ts`:
- Around line 151-157: Update containedPath to pass an explicit x g–appropriate
fix when constructing ScaffoldPathEscapeError, matching the existing
resolveLocales pattern; preserve the current path validation and error path
while ensuring the thrown error’s fix command repairs the generate command
rather than using the default scaffold gate command.
In `@packages/jobs/src/dsl.test.ts`:
- Around line 162-173: Update the test around defineRecordingTask and
recorder.handle.entries/describe to avoid direct Date.now() calls. Stub or
inject the jobs-package Clock with a fixed epoch-millisecond value, then assert
both recorded occurrences equal that exact value while preserving the existing
two-entry coverage.
In `@scripts/boundaries.test.ts`:
- Around line 158-161: Remove the explicit throw from the undefined guard in the
test around sharedLeafFindingFor; retain the assertion and narrow leak without
using a bare Error, while preserving the existing finding assertions and test
behavior.
---
Outside diff comments:
In `@examples/dummy/apps/web/app/digest/jobs.ts`:
- Around line 56-80: Update the digest job’s load-posts flow and the underlying
service/repository contract to query a bounded [start, end) interval instead of
using publishedSince(orgId, since). Derive the local-day start and exclusive end
from input.slotAt and member.tz, preserving the scheduled local date across DST
transitions, then pass both boundaries through the relevant post-query symbols.
- Around line 15-46: Update sendDigest’s input and scheduling flow to carry the
scheduled occurrence epoch milliseconds in the payload, rather than deriving it
from ctx.now(). Pass this occurrence value to scheduleByZone(), and propagate
the same value through deliverDigest’s slotAt so retries preserve the original
digest occurrence.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 31b35ca0-5953-4794-9545-77596bda0bf3
📒 Files selected for processing (72)
docs/architecture/03-request-lifecycle.mddocs/architecture/05-type-chain.mddocs/architecture/06-data-layer.mddocs/architecture/08-jobs-internals.mddocs/architecture/12-generated-app.mddocs/architecture/14-testing-internals.mddocs/architecture/15-adding-a-feature.mddocs/idea/02-primitives.mddocs/idea/04-jobs.mddocs/idea/05-caching.mddocs/idea/10-testing.mdexamples/dummy/app.config.tsexamples/dummy/apps/web/api/index.tsexamples/dummy/apps/web/api/tasks.tsexamples/dummy/apps/web/app/digest/jobs.tsexamples/dummy/apps/web/app/orgs/jobs.job.test.tsexamples/dummy/apps/web/app/posts/[id].tsxexamples/dummy/apps/web/app/posts/actions.contract.test.tsexamples/dummy/apps/web/app/posts/actions.tsexamples/dummy/apps/web/app/posts/live.live.test.tsexamples/dummy/apps/web/app/posts/live.tsexamples/dummy/apps/web/app/posts/mutator.test.tsexamples/dummy/apps/web/app/posts/mutator.tsexamples/dummy/apps/web/app/posts/policy.test.tsexamples/dummy/apps/web/app/posts/policy.tsexamples/dummy/apps/web/app/posts/repo.tsexamples/dummy/apps/web/app/posts/service.tsexamples/dummy/apps/web/app/posts/ui/like-button.tsxexamples/dummy/apps/web/shared/client.tsexamples/dummy/apps/web/shared/services.tsexamples/dummy/packages/db/src/schema/posts.tsexamples/dummy/packages/db/src/tags.tsexamples/dummy/scripts/test-setup.tsexamples/dummy/type-chain.test.tspackages/action/README.mdpackages/action/src/action.tspackages/action/src/index.tspackages/action/src/invoke.test.tspackages/action/src/invoke.tspackages/action/src/policy-gate.tspackages/cli/src/cmd-generate.test.tspackages/cli/src/cmd-generate.tspackages/cli/src/errors.tspackages/cli/src/templates/index.tspackages/cli/src/templates/locales.test.tspackages/cli/src/templates/locales.tspackages/cli/src/templates/query.tspackages/cli/src/templates/resource.tspackages/cli/src/templates/route.tspackages/cli/src/templates/scaffold-repo.tspackages/core/src/context.test.tspackages/core/src/context.tspackages/jobs/CLAUDE.mdpackages/jobs/README.mdpackages/jobs/src/dsl.test.tspackages/jobs/src/scheduler.test.tspackages/jobs/src/scheduler.tspackages/mcp/src/from-action.tspackages/query/src/query.tspackages/testing/src/matchers.test.tsscripts/boundaries.test.tsscripts/boundaries.tsscripts/verify.tswiki/Actions.mdwiki/Caching-And-Invalidation.mdwiki/Error-Codes.mdwiki/Getting-Started.mdwiki/Jobs-And-Workflows.mdwiki/Policies-And-Authz.mdwiki/Testing.mdwiki/The-Eight-Primitives.mdwiki/Troubleshooting.md
- `toggleLike` is `likePost`: the operation only ever likes, and the doc snippets that still showed the incremental `likes + 1` local twin now show the convergent form the spec calls for - the native comment form carries `orgId`, so a pre-hydration POST satisfies `createComment`'s schema instead of failing validation - one `thrownBy` for the CLI package, failing through `expect.unreachable` rather than a bare Error; three more copies in parse.test.ts deleted, two of which could never fail - `containedPath` throws with the `x g` fix, not the scaffold gate's - the jobs DSL test reads `nowMs()` — the package's own Clock seam — and asserts one exact instant instead of a window - `node:process` in the type-chain test, boundaries test narrows without a bare Error Co-Authored-By: Claude <noreply@anthropic.com>
Summary
x newscaffolder against the fluent surface (t,.as(),.contract(),.tool(),.enqueue(),$view,$describe,$parse)actions,live,jobs,mutator,policy) to the canonical DSL shapes fromdocs/idea/02-primitives.mdx g resourcenow emits all 16 designed artifacts (admin, UI, i18n, manifest wiring included)docs/architecture/05-type-chain.md: newexamples/dummy/type-chain.test.tsrenamesexcerpt→summaryin the real schema, typechecks before/after with the real compiler, and pins the exact diff. TiesPostViewto thepostsentity viasatisfies Record<PostViewKeys, unknown>so hop 4 (entity → view schema) fails to compile on a renamed/dropped column instead of drifting silently. Hops 5+ are out of scope — they run throughpackages/db's repo/query layer, which PR 5 (data substrate) has not built yet.Test plan
bun run verify— 15/15 greencd examples/dummy && bun test— 29 pass / 20 fail, unchanged pre-existing baseline (fixture/provisioning gaps owned by other tasks)bun test type-chain.test.ts— 2/2 pass, schema file restored on both success and failure paths🤖 Generated with Claude Code
Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.Summary by CodeRabbit
New Features
Improvements