Skip to content

Adversarial audit: correctness fixes and invariant guardrails - #27

Open
k-taro56 wants to merge 25 commits into
mainfrom
eng-971
Open

Adversarial audit: correctness fixes and invariant guardrails#27
k-taro56 wants to merge 25 commits into
mainfrom
eng-971

Conversation

@k-taro56

@k-taro56 k-taro56 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Repo-wide adversarial audit of the control plane, drivers, and supervisor,
followed by the fixes and guardrails it surfaced. Every documented invariant
was probed against the code; the result is a small set of correctness fixes
plus automated gates for invariants that were previously enforced by review
alone. A follow-up code review of these changes (and two rounds of adversarial
self-review) is folded in.

No behavioral change to the data/control paths beyond the fixes below; the bulk
is defensive hardening and tests.

Correctness fixes

  • Sky YAML rendered with the YAML-1.1 schema. SkyPilot/SkyServe load task
    and service YAML with PyYAML (YAML 1.1); the default 1.2 output emitted
    off/no/12:34:56-style string envs as plain scalars that a 1.1 loader
    reinterprets as booleans/base-60 ints. Serialize through a shared
    stringifySkyYaml so the schema choice lives in one place.
  • joinUrl fails closed on base-path escape. A .. segment in path could
    climb above the base path prefix joinUrl exists to preserve, without moving
    the origin (so the origin backstop missed it). Added a segment-boundary prefix
    backstop.
  • Degraded-escalation grace moved inside the CAS. The grace window was
    checked only against the tick's in-memory snapshot; a concurrent reconciler
    that recovered-then-re-degraded the active could escalate before the current
    degraded period reached the grace. The guard now rides inside the escalation
    UPDATE against the live stateUpdatedAt.
  • escalateDomainIfFleetIdle takes a named budgets object. Its two trailing
    duration params (which default to different values, 30s vs 60s) were adjacent
    positional numbers a caller could transpose undetectably; they are now a
    Pick<FleetPolicy, ...> object.
  • db:seed exits cleanly on a bad layout file instead of a raw stack trace.

Guardrails (invariants that had no automated enforcement)

  • db.transaction() closed at the type. transaction is Omitted from
    HaruDatabase so an interactive-transaction call (which throws at runtime only
    on Neon HTTP, but passes on the PGlite/Postgres test lanes) is now a compile
    error; a lint rule remains as a secondary guard for a raw drizzle handle.
  • README env-var contract drift test — the haru-server environment table
    must match serverEnvironmentSchema (the JSON Schemas and migrations were
    already drift-gated; this prose contract was not).
  • Publishability gate — scans source and shipped layouts/seeds for a
    denylist of specific GPU/LLM model names (the "workloads are pure data" rule
    was human-reviewed only). Includes a positive control and a per-line
    publishability-allow escape hatch.
  • Concurrent switch_active vs target_not_routed race test on the Postgres
    lane, exercising the block-then-unblock interleave the routingCommitted
    column guard exists for.

Docs

  • KNOWN_ISSUES.md/.ja.md, AGENTS.md, and the README env contract kept in
    sync (EN/JA pairs updated together).

Testing

pnpm build && pnpm typecheck && pnpm lint && pnpm format:check all green;
pnpm test = 359 tests passing across all 7 packages; pnpm install --frozen-lockfile consistent. The type-level transaction guard and both new
gates were verified with negative probes (a planted violation fails as
expected).


Summary by cubic

Repo-wide adversarial audit fixing correctness gaps and adding invariant guardrails across control plane, drivers, and supervisor; also reinforces the publishability gate to prevent silent weakening of its checks.

  • Bug Fixes

    • Render Sky YAML with YAML‑1.1 via shared stringifySkyYaml so values like off/12:34:56 stay strings for PyYAML; removed dead yaml dep in @haru/driver-skyserve.
    • joinUrl now rejects paths that escape the base prefix via .. (segment‑boundary check) in @haru/protocol.
    • Degraded→failed grace is re‑checked inside the escalation CAS using live stateUpdatedAt to close the concurrent‑reconciler race (@haru/db, haru-server).
    • escalateDomainIfFleetIdle now takes a named budgets object (Pick<FleetPolicy, ...>) to prevent positional swaps.
    • db:seed exits cleanly with a single‑line error; extracted formatSeedError and added tests.
  • Guardrails

    • Omit transaction from HaruDatabase and lint‑ban db.transaction(); enforce single‑statement CAS transitions.
    • README env‑var drift gate: table must match serverEnvironmentSchema; walker ignores fenced code, stops at same‑or‑higher headings, and throws on unterminated fences.
    • Publishability gate: policy moved to governed data (publishability-denylist.txt + per‑token publishability-samples.txt), strict loader fails on malformed/empty/empty‑label rules, structured violations use exact label matching, exact sample count pinned, scans all shipped modules (.ts/.mts/.cts/.mjs/.cjs/.js), docs (.md/.yaml/.yml), and JSON across packages/, services/, .github/, and root files; policy/samples are exempt from scanning but used as end‑to‑end positives; broadened GPU/LLM patterns; per‑line publishability-allow escape; scope limits documented in KNOWN_ISSUES (EN/JA).
    • Race test for concurrent switch_active vs target_not_routed asserts mutual exclusion under real lock contention.

Written for commit 992f924. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes
    • Prevented URL path traversal outside the configured prefix.
    • Improved YAML rendering to quote ambiguous YAML 1.1 values (SkyPilot/SkyServe).
    • Strengthened failover/escalation timing so degraded escalation respects the grace window and avoids stale timestamp races.
    • Improved seed command failure handling to output cleaner errors and exit non-zero.
  • Documentation
    • Clarified transaction usage restrictions, escalation safeguards, and publishability gate limitations (including deferred consumer-repo identifier scanning).
  • Tests
    • Added/expanded coverage for URL traversal, concurrent routing outcomes, escalation grace behavior, YAML quoting, publishability gate policy/samples, and server environment contract sync.

k-taro56 added 17 commits July 22, 2026 17:24
…rings

SkyPilot/SkyServe load task and service YAML with PyYAML, whose implicit
resolvers are YAML 1.1. The yaml package's default 1.2 core schema emits
values like off/no/y and sexagesimal 12:34:56 as plain scalars, which a
1.1 loader reinterprets as booleans/base-60 integers, corrupting or
rejecting operator-supplied env values. Serialize with the yaml-1.1
schema, which quotes exactly those ambiguous scalars while leaving real
booleans and multi-line block scalars untouched.
The origin backstop only catches host swaps; a ".." in path is resolved
by the URL parser and can climb above the base path prefix that joinUrl
exists to preserve (joinUrl("https://h/api", "/../x") -> "https://h/x")
without moving the origin. Add a second backstop asserting the resolved
pathname still sits under the prefix at a segment boundary, consistent
with the module's fail-closed posture. Every caller passes a code-literal
path today, so this is defense in depth.
detectDegradedEscalation computed degradedForMs from the tick's in-memory
snapshot, but escalateDomainIfFleetIdle only guarded state='degraded' -
not stateUpdatedAt. Under concurrent reconcilers, a second tick could
recover then re-degrade the active (fresh stateUpdatedAt) between the
first tick's snapshot load and its escalation CAS, letting the first tick
escalate before the CURRENT degraded period reached degradedGraceMs.

Re-check the live stateUpdatedAt against the injected clock inside the
UPDATE (stateUpdatedAt < at - degradedGraceMs, strict to match core's
degradedForMs > grace). Adds a cas.test.ts case modelling the race and
updates the mirrored-invariant notes in failover.ts and AGENTS.md.
main() was awaited at the top level with no catch, so a missing/invalid
layout file (readFileSync/JSON.parse) or a schema validation failure
surfaced as an unhandled rejection. Wrap it to log a one-line message and
exit(1), matching the DATABASE_URL guard's behavior.
The production DB handle is drizzle-orm/neon-http, whose HTTP driver has
no interactive transactions: db.transaction() typechecks, passes on both
PGlite and the real-Postgres CI lane, and throws only at runtime on Neon,
which nothing in CI exercises. AGENTS.md forbids it but nothing enforced
that. Add a no-restricted-syntax selector banning any .transaction( call
(zero legitimate uses exist today).
The JSON Schemas and drizzle migrations are drift-gated in CI, but the
prose 'haru-server environment' table was not, so a var added to or
removed from serverEnvironmentSchema could silently desync from the docs
AGENTS.md requires kept in sync. Parse the table's variable names and
assert they equal the schema keys in both directions.
AGENTS.md forbids specific model or GPU names in code, seeds, or example
layouts, but only human review enforced it. Scan packages/ and services/
sources plus example JSON for a denylist of GPU model identifiers and LLM
model families. nvidia-smi (a required tool name) and vLLM (the engine)
are deliberately allowed; the private-repo/infra half stays human
-reviewed since the only org name present is the repo's own publisher.
The routing-commit suite only exercised the guard sequentially (commit
then fail). Add a Promise.all race so the CI Postgres lane drives the
genuine block-then-unblock interleave the routingCommitted-column guard
exists for: the loser blocks on the operation row lock, then re-evaluates
under READ COMMITTED on unblock. Asserts commit and a target_not_routed
fail are never both applied, and the persisted state matches the winner,
under either ordering (PGlite serializes and still proves winner/loser).
The new publishability test covers model/GPU names but leaves the
private-repo/infra half of the AGENTS.md rule to human review, since the
only org name in the tree is the publisher's own. Record that as a
deferred item in both KNOWN_ISSUES pairs.
Follow-up to the audit-hardening tests: guard a possibly-undefined regex
group, use Set#difference and non-.forEach iteration, extract deeply
nested calls into locals, rename the boolean locals to the is-prefixed
form unicorn requires, rename environment-docs.test.ts (docs abbreviation)
to environment-contract.test.ts, and apply oxfmt. No behavior change.
Address code-review findings on the new gate:
- GPU denylist now covers Blackwell (B100/B200/B300, GB300) and AMD MI300A
  (the MI branch accepts a trailing A or X), not just Hopper/Ampere.
- LLM denylist adds falcon/nemotron/granite/internlm/baichuan/command-r/
  dbrx/wizardlm/yi-N, so common open-weight families no longer slip past.
- Scan all shipped data (any layout/seed/schema JSON), not only /examples/,
  matching the AGENTS.md 'code, seeds, or example layouts' scope.
- Extract violationsInFile with a whole-file fast path (regex per line only
  on a real hit), flattening the former 4-level nested loop.
escalateDomainIfFleetIdle took heartbeatStaleMs and degradedGraceMs as
adjacent positional numbers that default to DIFFERENT values (30s vs 60s),
and every test passed them equal (30_000/30_000), so a transposed call
site would compile and pass all tests while swapping the budgets in
production. Collapse them into a Pick<FleetPolicy, ...> object so the
fields are named (a swap is now impossible) and tied to the policy source
of truth. Also extract the repeated degrade-before-grace test setup into a
degradeAlphaBeforeGrace() helper.
The lint ban was bypassable (destructuring, casts, a raw drizzle handle).
Omit "transaction" from HaruDatabase so any interactive-transaction call
is a compile error; the full drizzle instance stays assignable (supertype).
Keep the lint as a secondary guard for a call on a raw drizzle handle
before it is typed, and update AGENTS.md to match.
…lace

Both renderers duplicated stringify(x, { schema: "yaml-1.1" }) and its
PyYAML rationale, coupled only by prose; one side could silently drift back
to YAML-1.2. Move the option into stringifySkyYaml in driver-skypilot
(skyserve already imports placementToResources from it) and call it from
both.
…nces

The walker broke on the first line starting with '#', so a future '####'
sub-note or a '#'-comment inside a fenced code block in the section would
truncate the documented-var set and fail the drift gate spuriously. Break
only on a same-or-higher-level heading (# / ## / ###) outside a ``` fence.
Extracting stringifySkyYaml into @haru/driver-skypilot removed skyserve's
only direct `yaml` import, so the dependency is dead weight. Remove it;
skyserve reaches the serializer through its existing @haru/driver-skypilot
workspace dependency.
Address completeness-critic gaps from the adversarial self-review:
- publishability: add a positive control (a non-trivial file set is
  scanned AND the full matcher flags this test file's own tokens), so a
  green toEqual([]) cannot mean 'scanned nothing' or 'matcher is vacuous';
  add a publishability-allow line marker as an escape hatch for a
  legitimate token that collides with a denylist word.
- environment-contract: skip the WHOLE line inside a code fence, not just
  the heading break, so a fenced pipe-shaped sample line is not miscounted
  as a documented var.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

The PR tightens degraded-domain escalation with an in-CAS grace check, blocks interactive transactions, adds publishability scanning, centralizes YAML 1.1 serialization, hardens URL prefix handling, improves seed errors, and adds concurrency and environment-contract tests.

Changes

Concurrency and database guard updates

Layer / File(s) Summary
Interactive transaction enforcement
AGENTS.md, eslint.config.ts, packages/db/src/client.ts
Interactive transactions are documented as prohibited, lint-rejected, and omitted from the exported database type.
Degraded escalation CAS guard
packages/db/src/repo/domains.ts, packages/core/src/failover.ts, services/haru-server/src/reconciler/reconciler.ts, AGENTS.md
Escalation now receives heartbeat and grace budgets and requires live stateUpdatedAt to precede the grace cutoff.
Escalation race regression tests
packages/db/src/cas.test.ts, services/haru-server/src/steps-race.test.ts
Tests cover grace-window rejection, delayed success, and in-flight operation guards.
Concurrent routing outcome validation
packages/db/src/routing-commit.test.ts
Concurrent switching and failure cleanup are asserted to produce exactly one winner with matching persisted state.

Publishability scanning

Layer / File(s) Summary
Denylist scanner and coverage documentation
packages/db/src/publishability.test.ts, packages/db/src/publishability-denylist.txt, packages/db/src/publishability-samples.txt, AGENTS.md, KNOWN_ISSUES.md, KNOWN_ISSUES.ja.md
Repository files are scanned for denylisted model and GPU identifiers, with policy samples and documented consumer-organization coverage limits.

SkyPilot and SkyServe YAML serialization

Layer / File(s) Summary
Shared YAML 1.1 serializer
packages/driver-skypilot/src/yaml.ts, packages/driver-skyserve/src/yaml.ts, packages/driver-skyserve/package.json
Sky task and service rendering use the shared YAML 1.1 serializer.
YAML rendering tests
packages/driver-skypilot/src/driver.test.ts, packages/driver-skyserve/src/driver.test.ts
Ambiguous string values are verified as quoted in rendered YAML.

URL path-prefix protection

Layer / File(s) Summary
Path-prefix traversal validation
packages/protocol/src/url.ts, packages/protocol/src/protocol.test.ts
joinUrl rejects paths escaping a non-empty base prefix and permits normalization that remains within the prefix.

Operational script and environment contract validation

Layer / File(s) Summary
Seed error handling and environment synchronization
packages/db/src/seed-error.ts, packages/db/src/seed-error.test.ts, packages/db/src/seed.ts, services/haru-server/src/environment-contract.test.ts
Seed failures produce a single-line error and exit status one; README environment variables are checked against the runtime schema.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • arkorlab/haru#1: Earlier degraded escalation changes involving the same repository function, reconciler call site, and tests.
  • arkorlab/haru#6: Related degraded escalation CAS guard and freshness-condition changes.
  • arkorlab/haru#15: Related joinUrl path-handling changes and protocol tests.

Suggested reviewers: soleil-colza

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 100.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR’s broad focus on correctness fixes and added invariant guardrails.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch eng-971
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch eng-971

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@drift-check

drift-check Bot commented Jul 23, 2026

Copy link
Copy Markdown

Code Review Bot

No comment/code divergences or documentation drift detected. Reviewed 24 file(s); skipped 2.

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds correctness fixes and automated invariant guardrails across URL handling, YAML serialization, failover escalation, database usage, seed errors, and documentation checks.

  • Serializes SkyPilot and SkyServe configuration using the YAML 1.1 schema.
  • Prevents joinUrl paths from escaping a configured base-path prefix.
  • Moves the degraded-grace check into the escalation compare-and-swap operation.
  • Removes interactive transactions from the public database handle and adds a lint guard.
  • Adds publishability, environment-contract, concurrency, and regression tests.
  • Formats seed failures as concise command-line errors.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains within the scope of this follow-up review.

Important Files Changed

Filename Overview
packages/db/src/repo/domains.ts Adds the live degraded-grace cutoff and named policy budgets to the atomic escalation guard.
services/haru-server/src/reconciler/reconciler.ts Passes named escalation budgets and preserves same-tick failover behavior after a successful escalation.
packages/protocol/src/url.ts Adds a segment-boundary check preventing resolved paths from escaping the base URL prefix.
packages/driver-skypilot/src/yaml.ts Introduces shared YAML 1.1 serialization for Sky task and service documents.
packages/driver-skyserve/src/yaml.ts Reuses the SkyPilot serializer and removes the package’s direct YAML serialization path.
packages/db/src/client.ts Omits interactive transactions from the driver-agnostic database type.
packages/db/src/publishability.test.ts Adds a denylist-backed repository publishability gate with parser and coverage controls.
services/haru-server/src/environment-contract.test.ts Adds a drift test matching documented server environment variables to the runtime schema.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Reconcile fleet] --> B[Poll heartbeats]
    B --> C{Operation in flight?}
    C -- Yes --> G[Continue operation reconciliation]
    C -- No --> D{Degraded escalation detected?}
    D -- No --> H[Evaluate normal failover]
    D -- Yes --> E[Escalation CAS checks live grace, pointer, fleet idle, and viable standby]
    E --> F{CAS succeeded?}
    F -- No --> H
    F -- Yes --> I[Mark active failed and append audit event]
    I --> H
Loading

Reviews (4): Last reviewed commit: "Close three silent-weakening paths in th..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 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 `@packages/db/src/publishability.test.ts`:
- Around line 55-62: Update isScannable to exclude only the scanner’s own file
by comparing each entry’s absolute path with the path derived from
fileURLToPath(import.meta.url), rather than comparing entry.name to SELF.
Preserve scanning for same-named publishability.test.ts files in other packages
or services, and remove or stop using SELF as needed.
- Around line 138-140: Replace the brittle scanned.length threshold in the
publishability coverage test with explicit assertions for representative
required files or roots, including their expected extensions. Keep the
violationsInFile(selfPath) assertion unchanged and ensure the sentinels directly
verify the scanner covers the required locations.
- Around line 46-72: The publishability gate must explicitly prove it scans the
complete governed file set instead of relying on an arbitrary count. Update
isScannable and the scan-root configuration to cover every required repository
root and text-file extension, or explicitly document and test intentional
exclusions; then replace the 20-file threshold near the gate assertion with
explicit sentinel files, roots, and extensions that verify coverage. Apply these
changes at packages/db/src/publishability.test.ts:46-72 and
packages/db/src/publishability.test.ts:138-140, using isScannable and the
existing scan assertion symbols.
- Around line 24-42: Remove the literal restricted model and GPU identifiers
from DENYLIST and its comments in the publishability test. Move these patterns
into an approved CI input/config consumed by the test, or encode the policy
without embedding the names in repository code; ensure the test still rejects
the same categories of identifiers without excluding publishability.test.ts from
scanning.

In `@packages/db/src/seed.ts`:
- Around line 65-67: Update the error formatting in the seed failure handler to
normalize embedded newline characters before passing the message to
console.error. Preserve the existing Error-versus-string extraction while
ensuring the resulting “seed failed” output is always a single line.

In `@services/haru-server/src/environment-contract.test.ts`:
- Around line 17-21: Update documentedServerVariables to accept README text as
an argument and retain only the environment-table parsing there. Move README
path construction and readFileSync into the test boundary, passing the loaded
text to documentedServerVariables so parser behavior can use fixture input
without filesystem I/O.
🪄 Autofix (Beta)

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.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 43a8227a-9129-421d-b63b-8a8c07cc723f

📥 Commits

Reviewing files that changed from the base of the PR and between 9bf0062 and 0bbf5fa.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (21)
  • AGENTS.md
  • KNOWN_ISSUES.ja.md
  • KNOWN_ISSUES.md
  • eslint.config.ts
  • packages/core/src/failover.ts
  • packages/db/src/cas.test.ts
  • packages/db/src/client.ts
  • packages/db/src/publishability.test.ts
  • packages/db/src/repo/domains.ts
  • packages/db/src/routing-commit.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/driver-skypilot/src/yaml.ts
  • packages/driver-skyserve/package.json
  • packages/driver-skyserve/src/driver.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/protocol.test.ts
  • packages/protocol/src/url.ts
  • services/haru-server/src/environment-contract.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • services/haru-server/src/steps-race.test.ts
💤 Files with no reviewable changes (1)
  • packages/driver-skyserve/package.json
📜 Review details
⏰ Context from checks skipped due to timeout. (3)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Seer Code Review
  • GitHub Check: check
🧰 Additional context used
📓 Path-based instructions (17)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep new I/O behind injectable boundaries so it can be tested without GPUs, cloud accounts, or a running database.

新しい I/O は注入可能な境界の背後に配置し、外部実行、fetch、子プロセス、タイマーなどをテストダブルに置き換えられるようにする。

**/*.{ts,tsx}: Respect the dependency graph: protocol may use only Zod and Node built-ins; core must remain pure with no I/O; db depends on core; supervisor depends on protocol only; shared server/supervisor code belongs in protocol.
Build outbound URLs with joinUrl from @haru/protocol; do not use new URL('/path', base) when the base may contain a path prefix.

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/url.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/yaml.ts
  • eslint.config.ts
  • packages/db/src/client.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • packages/db/src/routing-commit.test.ts
  • packages/core/src/failover.ts
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add a Vitest case next to changed code when introducing or modifying behavior.

変更したコードの近くに Vitest のテストを追加し、外部 I/O は注入可能な境界に対してテストする。

Use @haru/db/testing with committed migrations and avoid per-test migration calls; use injectable I/O, fake supervisors, and fake timers so tests require no GPUs, cloud accounts, or live database.

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • packages/db/src/routing-commit.test.ts
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • packages/db/src/cas.test.ts
**/*.{ts,tsx,js,jsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use oxfmt as the owner of formatting, including whitespace, wrapping, quotes, and trailing commas; do not hand-tune formatting for ESLint.

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/url.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/yaml.ts
  • KNOWN_ISSUES.md
  • eslint.config.ts
  • packages/db/src/client.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • packages/db/src/routing-commit.test.ts
  • packages/core/src/failover.ts
  • KNOWN_ISSUES.ja.md
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • AGENTS.md
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run both root-configured linters, oxlint --type-aware followed by strict type-aware ESLint 10; add overrides at the repository root rather than per-package configs.

**/*.{ts,tsx,js,jsx}: oxlint の型認識 lint を実行した後、型情報ベースの strict ESLint 10 を実行する。設定はパッケージごとではなくリポジトリルートに置き、例外には理由をコメントしたスコープ付きオーバーライドを優先する。
コード内のコメントは英語で記述する。

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/url.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/yaml.ts
  • eslint.config.ts
  • packages/db/src/client.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • packages/db/src/routing-commit.test.ts
  • packages/core/src/failover.ts
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Use kebab-case for file names.
Write comments in English.
Do not use the em dash character (U+2014) in code or prose; use a colon, comma, parentheses, or spaced hyphen instead.

ファイル名は kebab-case にする。

Code, comments, tests, documentation, seeds, and example layouts must not reference private consumer repositories or infrastructure, and must not contain specific model or GPU names.

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/url.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/yaml.ts
  • KNOWN_ISSUES.md
  • eslint.config.ts
  • packages/db/src/client.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • packages/db/src/routing-commit.test.ts
  • packages/core/src/failover.ts
  • KNOWN_ISSUES.ja.md
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • AGENTS.md
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
services/haru-server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run server suites against in-memory PGlite with committed Drizzle migrations so compare-and-swap SQL guarding state transitions is exercised, including concurrent-winner races.

Files:

  • services/haru-server/src/environment-contract.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • services/haru-server/src/steps-race.test.ts
**/*.{ts,tsx,js,jsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (CONTRIBUTING.ja.md)

oxfmt を使用して、空白、折り返し、クォート、末尾カンマを整形する。整形確認には pnpm format:check を使用する。

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/url.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/yaml.ts
  • KNOWN_ISSUES.md
  • eslint.config.ts
  • packages/db/src/client.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • packages/db/src/routing-commit.test.ts
  • packages/core/src/failover.ts
  • KNOWN_ISSUES.ja.md
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • AGENTS.md
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
**/*.{ts,tsx,js,jsx,md}

📄 CodeRabbit inference engine (CONTRIBUTING.ja.md)

コードと文章ではエムダッシュ (U+2014) を使用せず、コロン、コンマ、括弧、またはスペース付きハイフンを使用する。

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/url.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/yaml.ts
  • KNOWN_ISSUES.md
  • eslint.config.ts
  • packages/db/src/client.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • packages/db/src/routing-commit.test.ts
  • packages/core/src/failover.ts
  • KNOWN_ISSUES.ja.md
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • AGENTS.md
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
services/haru-server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

services/haru-server/src/**/*.{ts,tsx}: Use switchActive as the only writer of fleets.activeDomainId, and preserve its atomic operation guard and operations.routingCommitted update.
Keep the reconciler re-entrant and check-and-nudge based: issue at most one step nudge per tick, make executors safe to rerun, poll long operations, and apply outcomes through the single CAS-and-audit path.
Use the injected application clock for stepStartedAt and domains.stateUpdatedAt; do not replace it with database now().
Map supervisor failures through withSupervisor: target-domain 401/403 responses fail immediately, other failures remain pending until policy expiry, source-step failures remain pending, and response parsing must stay inside SupervisorError handling.
Degraded escalation must retain all guards inside the escalation update: grace period, auto-failover, viable standby, no in-flight operation, and an unchanged active routing pointer.
Completion checks over supervisor-reported lists must require length > 0 && every(...); empty lists must not produce vacuous success.
Keep vLLM sleep/wake endpoints private and localhost-only; expose external control through the bearer-authenticated supervisor API, and have the chat proxy construct only /v1/chat/completions paths.
The chat proxy must forward request bodies as raw text, copy only content-type plus the stale-routing header, bound only TTFB with the abort timer, abort upstream on pre-header disconnect, and use lowercase model routing keys with findRoutableBinding.
The data path may fail open only on an unreachable state store using the last valid cached snapshot; control routes must fail closed. A successful pointer read followed by a moved revision or malformed snapshot must never serve stale data.
/healthz must never access the database, and pointer lookup exceptions must remain distinct from a null result: null means the fleet is gone, while an exception means retain the cache entry.
Use case-i...

Files:

  • services/haru-server/src/environment-contract.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • services/haru-server/src/steps-race.test.ts
**/*.{ts,tsx,md}

📄 CodeRabbit inference engine (AGENTS.md)

Use English code comments and avoid the em dash character U+2014 in code and prose.

Files:

  • services/haru-server/src/environment-contract.test.ts
  • packages/driver-skyserve/src/yaml.ts
  • packages/protocol/src/url.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/db/src/seed.ts
  • packages/driver-skypilot/src/yaml.ts
  • KNOWN_ISSUES.md
  • eslint.config.ts
  • packages/db/src/client.ts
  • packages/driver-skyserve/src/driver.test.ts
  • packages/protocol/src/protocol.test.ts
  • services/haru-server/src/reconciler/reconciler.ts
  • packages/db/src/routing-commit.test.ts
  • packages/core/src/failover.ts
  • KNOWN_ISSUES.ja.md
  • packages/db/src/publishability.test.ts
  • services/haru-server/src/steps-race.test.ts
  • AGENTS.md
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
packages/driver-*/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Test driver integrations through an injectable exec boundary, covering recorded argv, timeout propagation, and error mapping; do not require a sky binary.

Files:

  • packages/driver-skyserve/src/yaml.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/driver-skypilot/src/yaml.ts
  • packages/driver-skyserve/src/driver.test.ts
packages/driver-*/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Drivers must not call cloud APIs; they must render placement data to SkyPilot/SkyServe YAML and execute through injectable argv-array exec without a shell.

Files:

  • packages/driver-skyserve/src/yaml.ts
  • packages/driver-skypilot/src/driver.test.ts
  • packages/driver-skypilot/src/yaml.ts
  • packages/driver-skyserve/src/driver.test.ts
packages/db/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Test @haru/db against in-memory PGlite using the committed Drizzle migrations, including compare-and-swap SQL and concurrent-winner races.

状態ストアの状態遷移では、compare-and-swap SQL によって並行実行時の勝者決定レースを保護する。

Schema edits must be accompanied by generated committed migrations under packages/db/drizzle; migration drift must not remain.

Files:

  • packages/db/src/seed.ts
  • packages/db/src/client.ts
  • packages/db/src/routing-commit.test.ts
  • packages/db/src/publishability.test.ts
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
packages/db/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

packages/db/src/**/*.{ts,tsx}: Implement every database state transition as a single-statement compare-and-swap, check the affected row count, and never use interactive db.transaction() or hold external work between a read and dependent write.
Keep one in-flight operation per fleet, preserve sourceDomainId at creation, and use it for post-commit cleanup rather than inferring the other domain.

Files:

  • packages/db/src/seed.ts
  • packages/db/src/client.ts
  • packages/db/src/routing-commit.test.ts
  • packages/db/src/publishability.test.ts
  • packages/db/src/repo/domains.ts
  • packages/db/src/cas.test.ts
**/{README,CONTRIBUTING,KNOWN_ISSUES}.md

📄 CodeRabbit inference engine (AGENTS.md)

Maintain English/Japanese documentation pairs together; update the corresponding .ja.md file whenever the English document changes.

Files:

  • KNOWN_ISSUES.md
packages/core/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

For state-machine changes in @haru/core, extend the exhaustive transition-table tests.

状態機械を変更する場合は、すべての状態遷移を網羅する遷移テーブルテストを拡張する。

Files:

  • packages/core/src/failover.ts
packages/core/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Treat the state tables in slot-state.ts and domain-state.ts as the single source of truth; repository code must reject invalid transitions, and shared predecessor lists must derive from statesWithEdgeTo.

Files:

  • packages/core/src/failover.ts
🪛 LanguageTool
KNOWN_ISSUES.md

[typographical] ~215-~215: The word ‘Where’ starts a question. Add a question mark (“?”) at the end of the sentence.
Context: ...packages/db/src/publishability.test.ts. - Current: the gate scans sources and e...

(WRB_QUESTION_MARK)


[typographical] ~223-~223: The word ‘Why’ starts a question. Add a question mark (“?”) at the end of the sentence.
Context: ... safely enumerated here without guessing. - Intended fix: when the consumer-org i...

(WRB_QUESTION_MARK)

AGENTS.md

[typographical] ~127-~127: To join two clauses or introduce examples, consider using an em dash.
Context: ...ride inside the escalation UPDATE itself - the grace re-checks the live `stateUpd...

(DASH_RULE)

🪛 OpenGrep (1.25.0)
services/haru-server/src/environment-contract.test.ts

[ERROR] 51-51: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

packages/db/src/publishability.test.ts

[ERROR] 109-109: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.

(coderabbit.command-injection.exec-js)

🔇 Additional comments (19)
packages/driver-skypilot/src/yaml.ts (1)

20-35: LGTM!

Also applies to: 48-48

packages/driver-skyserve/src/yaml.ts (1)

1-10: LGTM!

Also applies to: 26-26

packages/driver-skypilot/src/driver.test.ts (1)

78-99: LGTM!

packages/driver-skyserve/src/driver.test.ts (1)

61-74: LGTM!

AGENTS.md (1)

74-81: LGTM!

Also applies to: 126-130

eslint.config.ts (1)

123-138: LGTM!

packages/db/src/client.ts (1)

15-26: LGTM!

packages/db/src/repo/domains.ts (1)

10-10: LGTM!

Also applies to: 19-19, 57-59, 76-99, 114-114, 150-150

packages/core/src/failover.ts (1)

95-100: LGTM!

services/haru-server/src/reconciler/reconciler.ts (1)

644-659: LGTM!

packages/db/src/cas.test.ts (1)

33-45: LGTM!

Also applies to: 198-212, 223-243, 248-268, 273-318

services/haru-server/src/steps-race.test.ts (1)

350-358: LGTM!

Also applies to: 374-389

packages/db/src/routing-commit.test.ts (1)

185-240: LGTM!

services/haru-server/src/environment-contract.test.ts (1)

64-65: 🎯 Functional Correctness

No change needed. The repository targets Node >=24.10.0, and Set.prototype.difference is available in that baseline.

packages/db/src/publishability.test.ts (1)

1-23: LGTM!

Also applies to: 75-126, 128-137, 139-142

KNOWN_ISSUES.md (1)

213-227: LGTM!

KNOWN_ISSUES.ja.md (1)

202-216: LGTM!

packages/protocol/src/url.ts (1)

42-58: LGTM!

packages/protocol/src/protocol.test.ts (1)

686-708: LGTM!

Comment thread packages/db/src/publishability.test.ts Outdated
Comment thread packages/db/src/publishability.test.ts
Comment thread packages/db/src/publishability.test.ts Outdated
Comment on lines +55 to +62
// The scanner file names the very tokens it bans; excluding it keeps the
// gate from flagging itself.
const SELF = "publishability.test.ts";

function isScannable(name: string): boolean {
if (name === SELF) {
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Exclude only the exact scanner file.

SELF is compared with entry.name, so any other publishability.test.ts under packages or services is silently skipped. A denylisted token in that file would pass the gate. Compare the absolute path with fileURLToPath(import.meta.url) instead.

🤖 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/db/src/publishability.test.ts` around lines 55 - 62, Update
isScannable to exclude only the scanner’s own file by comparing each entry’s
absolute path with the path derived from fileURLToPath(import.meta.url), rather
than comparing entry.name to SELF. Preserve scanning for same-named
publishability.test.ts files in other packages or services, and remove or stop
using SELF as needed.

Comment thread packages/db/src/publishability.test.ts Outdated
Comment thread packages/db/src/seed.ts Outdated
Comment thread services/haru-server/src/environment-contract.test.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0bbf5fa287

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread packages/db/src/publishability.test.ts Outdated
// Instinct (MI250/MI300A/MI300X/MI325X), and RTX consumer cards. The
// MI branch allows a trailing A (APU) or X (accelerator) suffix.
pattern:
/\b(?:H100|H200|H800|B100|B200|B300|A100|A800|A6000|A40|V100|L40S|L40|GH200|GB200|GB300|MI\d{2,3}[AX]?|RTX ?\d{3,4})\b/i,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove hard-coded workload names from the gate

This new publishability gate hard-codes the exact GPU identifiers it is meant to keep out of the repository, and the model-family regex below does the same for LLM names. Since this file is test code and the scanner deliberately excludes itself, CI will stay green while the repository now contains the specific workload names the project forbids; move the denylist out of the publishable tree or encode the detector without literal model/GPU names.

AGENTS.md reference: AGENTS.md:L212-L215

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 22 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread packages/db/src/publishability.test.ts Outdated
Comment thread packages/db/src/publishability.test.ts Outdated
Comment thread packages/db/src/seed.ts Outdated
Comment thread packages/db/src/publishability.test.ts Outdated
k-taro56 added 7 commits July 23, 2026 17:05
Address PR review (CodeRabbit/Codex/cubic) on the publishability gate:
- The gate embedded the exact GPU/LLM identifiers it forbids in test CODE
  and excluded itself from scanning. Move the patterns into
  publishability-denylist.txt, a policy DATA file that is not a scanned
  source (AGENTS.md scopes the rule to code/seeds/layouts), so no model or
  GPU name lives in governed code and the self-exclusion hack is gone.
- Cover every shipped module extension (.ts/.mts/.cts/.mjs/.cjs/.js), not
  only .ts, so a leak in e.g. generate-schemas.mjs no longer passes.
- Replace the brittle >20 file-count positive control with explicit
  coverage sentinels (representative files across both roots and every
  kind) plus a non-vacuous check that each pattern matches its own policy.
A multi-issue Zod error or a JSON.parse error carries embedded newlines,
so the 'clean one-line message' the guard promises could still span lines.
Collapse CR/LF runs to a single space.
Per the injectable-I/O testing convention, documentedServerVariables now
takes README TEXT (the file read stays at the test boundary), so the
section walker can be exercised with inline fixtures. Add cases for a
sub-note heading, a fenced pipe-shaped sample line, and a missing
section - the edges the walker hardening was written for.
…trol

Adversarial self-review found the data-file move traded one vacuous-pass
risk for three worse ones, all silent:

- A policy file that parsed to zero rules (comments-only after a bad merge
  or truncation) left DENYLIST empty; `[].every(...)` is vacuously true,
  so every file scanned clean and BOTH tests stayed green with a real leak
  in the tree.
- A row written with spaces instead of a TAB compiled the WHOLE line as
  the regex - a dead rule that still matched its own policy text, so the
  self-check passed while it matched nothing in the repo. A CRLF checkout
  did the same via a trailing carriage return.
- The rewritten positive control asserted the raw regexes against the
  policy text and no longer called `violationsInFile`, leaving the actual
  scan pipeline untested: stubbing it to return [] kept the suite green.

Parse through a pure `parseDenylist(text)` that strips \r and THROWS on a
missing TAB, an empty pattern, or zero rules; assert the end-to-end
pipeline by scanning the policy file itself (it carries the tokens) and
requiring a reported hit per rule; add a test for each rejection. All four
mutants (dead scanner, inverted fast path, emptied policy, space-separated
row) now fail the suite.
The completeness critic showed the per-RULE non-vacuity check was still
too weak: both rules are large alternations, so deleting a single branch
(say the qwen alternative) left 'the pattern matches its own policy text'
true while that identifier stopped being detected. Add
publishability-samples.txt - one identifier per branch, governed data like
the policy itself - and assert every sample is reported through the real
matcher, which now lives in a pure violationsInText.

Also close the half-satisfied review ask: scan .github and the root-level
files (README/AGENTS/KNOWN_ISSUES/workflows) and the .md/.yaml/.yml
extensions, not just packages/services .ts, and pin isScannable's verdicts
- including the deliberate exclusions - in a test rather than leaving four
of six extensions aspirational. AGENTS.md now documents the sanctioned
data-file exemption that makes the design legal.
The newline collapse shipped with no coverage at all (seed.ts is a script
with a top-level await, so importing it to test would run the seed). Move
the formatting into seed-error.ts and test it against a real multi-issue
Zod error, which carries indented continuation lines - so collapse every
whitespace run, not just line breaks, or the one-line message comes out
with wide gaps mid-sentence.
An odd number of fences left isInFence true to EOF, swallowing the rest of
the section and the heading that ends it; the drift test then failed with
a baffling 'undocumented env var' diff instead of the markdown typo it is.
Throw on the unbalanced fence, and cover the two walker cases the fixtures
missed: a section ended by a higher-level heading, and the unterminated
fence itself.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 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 `@AGENTS.md`:
- Around line 223-224: The documentation and scanner behavior around the
publishability exemption must bind publishability-allow to the exact legitimate
collidee. Update the relevant denylist scanning logic and AGENTS.md guidance so
the marker is honored only when it appears on the same line as a governed
denylist token/rule collision, and add tests proving unrelated prohibited
references cannot bypass the gate.

In `@packages/db/src/publishability.test.ts`:
- Around line 58-62: Update the policy parsing logic around the denylist rule
construction to reject labels that are empty or contain only whitespace, before
adding the rule to rules. Preserve the existing empty-pattern validation and add
a malformed-policy test covering a blank label such as a tab-prefixed pattern.
- Around line 160-165: Add a Vitest regression case next to the publishability
matcher tests that compares equivalent marked and unmarked lines: assert the
line containing the `publishability-allow` marker produces no finding, while the
unmarked version is reported. Use the existing matcher/test helpers and preserve
their established assertion style.

In `@packages/db/src/seed-error.test.ts`:
- Around line 14-16: In the seed-error test, narrow the safeParse result after
the parsed.success assertion before accessing parsed.error in the
formatSeedError call. Add an explicit control-flow check or assertion that
establishes the failure branch, while preserving the existing runtime
expectation and error formatting behavior.

In `@packages/db/src/seed.ts`:
- Around line 66-67: Update main() error handling so the database client is
closed before any process.exit(1) call, including errors occurring after client
creation. Ensure cleanup runs reliably before exiting while preserving the
existing formatted error logging.

In `@services/haru-server/src/environment-contract.test.ts`:
- Line 41: Update the empty-section validation in the relevant
environment-contract test fixtures so it checks documented.size === 0 regardless
of isEnded, preserving the expected error when a heading terminates an otherwise
empty section. Add an empty-section fixture covering termination by ###, ##, or
# and apply the same correction to the other referenced cases.
🪄 Autofix (Beta)

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.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ff01af66-414e-49b7-b9c4-38fb5721c974

📥 Commits

Reviewing files that changed from the base of the PR and between 0bbf5fa and a1c377e.

📒 Files selected for processing (8)
  • AGENTS.md
  • packages/db/src/publishability-denylist.txt
  • packages/db/src/publishability-samples.txt
  • packages/db/src/publishability.test.ts
  • packages/db/src/seed-error.test.ts
  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • services/haru-server/src/environment-contract.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (4)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Seer Code Review
  • GitHub Check: check
  • GitHub Check: test-postgres
🧰 Additional context used
📓 Path-based instructions (14)
**/*

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*: Use kebab-case for file names.
Write comments in English.
Do not use the em dash character (U+2014) in code or prose; use a colon, comma, parentheses, or spaced hyphen instead.

ファイル名は kebab-case にする。

Keep the repository publishable: do not reference consumer-private repositories or infrastructure, and do not include specific model or GPU names in code, seeds, layouts, tests, comments, or documentation except in the governed publishability denylist and samples files.

Files:

  • packages/db/src/publishability-denylist.txt
  • packages/db/src/publishability-samples.txt
  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • AGENTS.md
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Keep new I/O behind injectable boundaries so it can be tested without GPUs, cloud accounts, or a running database.

新しい I/O は注入可能な境界の背後に配置し、外部実行、fetch、子プロセス、タイマーなどをテストダブルに置き換えられるようにする。

**/*.{ts,tsx}: Respect the dependency graph: @haru/protocol contains shared protocol helpers and types; @haru/core is pure logic with no I/O; @haru/db depends on core; services may use the appropriate lower layers; @haru-supervisor depends only on protocol.
Build outbound URLs with joinUrl from @haru/protocol; do not use new URL('/path', base) when the base may contain a path prefix.
Use the single root eslint.config.ts and oxlint.config.ts; add scoped overrides at the root with a reason instead of package-local configs.

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
**/*.{ts,tsx,js,jsx,json,md,yml,yaml}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Use oxfmt as the owner of formatting, including whitespace, wrapping, quotes, and trailing commas; do not hand-tune formatting for ESLint.

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • AGENTS.md
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run both root-configured linters, oxlint --type-aware followed by strict type-aware ESLint 10; add overrides at the repository root rather than per-package configs.

**/*.{ts,tsx,js,jsx}: oxlint の型認識 lint を実行した後、型情報ベースの strict ESLint 10 を実行する。設定はパッケージごとではなくリポジトリルートに置き、例外には理由をコメントしたスコープ付きオーバーライドを優先する。
コード内のコメントは英語で記述する。

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
packages/db/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Test @haru/db against in-memory PGlite using the committed Drizzle migrations, including compare-and-swap SQL and concurrent-winner races.

状態ストアの状態遷移では、compare-and-swap SQL によって並行実行時の勝者決定レースを保護する。

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • packages/db/src/publishability.test.ts
**/*.{ts,tsx,js,jsx,json,md,yaml,yml}

📄 CodeRabbit inference engine (CONTRIBUTING.ja.md)

oxfmt を使用して、空白、折り返し、クォート、末尾カンマを整形する。整形確認には pnpm format:check を使用する。

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • AGENTS.md
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
**/*.{ts,tsx,js,jsx,md}

📄 CodeRabbit inference engine (CONTRIBUTING.ja.md)

コードと文章ではエムダッシュ (U+2014) を使用せず、コロン、コンマ、括弧、またはスペース付きハイフンを使用する。

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • AGENTS.md
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
packages/db/src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

packages/db/src/**/*.ts: Implement every state transition as a single-statement compare-and-swap with an appropriate WHERE predicate and verify the affected row count. Do not use interactive db.transaction() through HaruDatabase or hold external work between a read and dependent write.
Preserve the one-in-flight-operation-per-fleet invariant, join the existing in-flight row on conflict, and record sourceDomainId at operation creation for post-commit cleanup.

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • packages/db/src/publishability.test.ts
**/*.{ts,tsx,md,mdx}

📄 CodeRabbit inference engine (AGENTS.md)

Use English code comments and avoid the em dash character U+2014 in code and prose.

Files:

  • packages/db/src/seed-error.ts
  • packages/db/src/seed.ts
  • packages/db/src/seed-error.test.ts
  • AGENTS.md
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Add a Vitest case next to changed code when introducing or modifying behavior.

変更したコードの近くに Vitest のテストを追加し、外部 I/O は注入可能な境界に対してテストする。

Files:

  • packages/db/src/seed-error.test.ts
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.test.{ts,tsx}: Use @haru/db/testing and its committed migrations for database tests; do not add per-test migration calls. State-machine changes must extend exhaustive transition-table tests.
Keep tests independent of GPUs, cloud accounts, and live databases by injecting fetch, exec, spawn, and clock dependencies; use Hono app.request() and fake supervisors for server tests.

Files:

  • packages/db/src/seed-error.test.ts
  • services/haru-server/src/environment-contract.test.ts
  • packages/db/src/publishability.test.ts
services/haru-server/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CONTRIBUTING.md)

Run server suites against in-memory PGlite with committed Drizzle migrations so compare-and-swap SQL guarding state transitions is exercised, including concurrent-winner races.

Files:

  • services/haru-server/src/environment-contract.test.ts
services/haru-server/src/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

services/haru-server/src/**/*.ts: Use switchActive as the only writer of fleets.activeDomainId, and preserve its atomic routing-commit and operation-guard behavior.
Use the injected application clock for stepStartedAt and domains.stateUpdatedAt; do not replace it with database now().
Route supervisor failures through withSupervisor: target-domain 401/403 responses fail the step immediately, while other failures remain pending until the policy budget expires; source steps treat authentication failures as pending. Keep response parsing inside SupervisorError wrapping.
Mirror supervisor-reported per-slot health using guarded CAS transitions; a running training report without a PID must not count as healthy.
Keep vLLM sleep/wake admin endpoints private and loopback-only; expose external control only through the supervisor's bearer-authenticated API.
Use model as a lowercase routing key and resolve it through the same per-model predicate route intent uses, including findRoutableBinding in core.
The data path may fail open only when the pointer read fails: serve the last valid cached snapshot with X-Haru-Routing: stale; control routes must fail closed.
After a successful pointer read, fail closed if the pointer revision moved or the snapshot is malformed; only a revision-matching snapshot cached at the current load may be served.
Distinguish a pointer lookup returning null from throwing: null means the fleet is gone and may be forgotten; a thrown lookup means the store is unavailable and the cache entry must be retained.
Fleet-reference identity must be case-insensitive for UUID IDs but case-sensitive for slugs and aliases; cache indexing must preserve the database ID-first rule using isFleetIdShaped.
Do not publish an overlapping snapshot load when the fleet was forgotten, a newer revision was cached, or a slug-fallback result has a different ID; retain later knowledge while building only the response from the losing snapshot.

Files:

  • services/haru-server/src/environment-contract.test.ts
services/haru-server/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

services/haru-server/src/**/*.{ts,tsx}: The chat proxy must forward request bodies as raw text, copy only content-type plus the permitted stale-routing header, bound only TTFB with its abort timer, abort upstream on pre-header client disconnect, and construct only /v1/chat/completions paths.
/healthz must never access the database.

Files:

  • services/haru-server/src/environment-contract.test.ts
🪛 ast-grep (0.44.1)
packages/db/src/publishability.test.ts

[warning] 61-61: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(source, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity

(regexp-from-variable)


[warning] 61-61: Do not use variable for regular expressions
Context: new RegExp(source, "i")
Note: [CWE-1333] Inefficient Regular Expression Complexity. Security best practice.

(regexp-non-literal-typescript)

🪛 LanguageTool
packages/db/src/publishability-denylist.txt

[style] ~4-~4: Since ownership is already implied, this phrasing may be redundant.
Context: ... so the publishability gate never flags its own policy. # (AGENTS.md scopes the no-mode...

(PRP_OWN)


[grammar] ~7-~7: Please add a punctuation mark at the end of paragraph.
Context: ...x\b|\bwizardlm\b|\byi-\d|\b(?:gpt|phi)-\d

(PUNCTUATION_PARAGRAPH_END)

packages/db/src/publishability-samples.txt

[style] ~4-~4: Since ownership is already implied, this phrasing may be redundant.
Context: ...ives are deleted, so every branch needs its own # sample here. Like the policy file thi...

(PRP_OWN)

AGENTS.md

[uncategorized] ~217-~217: The official name of this software platform is spelled with a capital “H”.
Context: ...u/db, scanning the packages/services/.github trees plus the root-level files). Its O...

(GITHUB)

🔇 Additional comments (7)
packages/db/src/publishability-denylist.txt (1)

1-7: LGTM!

packages/db/src/publishability-samples.txt (1)

1-47: LGTM!

packages/db/src/publishability.test.ts (1)

10-57: LGTM!

Also applies to: 70-159, 177-309

packages/db/src/seed-error.ts (1)

1-15: LGTM!

packages/db/src/seed-error.test.ts (1)

1-13: LGTM!

Also applies to: 17-32

packages/db/src/seed.ts (1)

6-6: LGTM!

services/haru-server/src/environment-contract.test.ts (1)

16-18: LGTM!

Also applies to: 20-25, 27-28, 64-71, 82-84, 100-154

Comment thread AGENTS.md
Comment on lines +223 to +224
sample whenever you add a denylist branch. A line with a legitimate
colliding token opts out with a `publishability-allow` marker.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

rg -n -C 8 \
  'publishability-allow|publishability-(denylist|samples)' \
  packages/db/src

Repository: arkorlab/haru

Length of output: 6762


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

printf '%s\n' '--- packages/db/src/publishability.test.ts lines 120-180 ---'
sed -n '120,180p' packages/db/src/publishability.test.ts | nl -ba -v120

printf '%s\n' '--- tests around publishability-allow ---'
rg -n -C 6 'publishability-allow|allow' packages/db/src/publishability.test.ts

printf '%s\n' '--- sample/denylist files ---'
for f in packages/db/src/publishability-samples.txt packages/db/src/publishability-denylist.txt; do
  if [ -f "$f" ]; then
    printf '\n### %s\n' "$f"
    wc -l "$f"
    sed -n '1,220p' "$f"
  fi
done

printf '%s\n' '--- AGENTS section lines 210-230 ---'
sed -n '210,230p' AGENTS.md | nl -ba -v210

Repository: arkorlab/haru

Length of output: 252


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

tests = Path("packages/db/src/publishability.test.ts").read_text()
print("contains allow before scanning hit:", "if (line.includes(\"publishability-allow\"))" in tests)
print("contains same-line collision check:", any(x in tests for x in [
  'publishability-allow',
  'line === "publishability-allow"',
  'trim() === "publishability-allow"',
  'includes("publishability-allow") && .*pattern',
]))

# Extract loop body roughly and print whether allow branch has an enclosing denylist-pattern test before continue.
start = tests.index('for (const [index, line] of content.split("\\n").entries())')
block = tests[start:start + 120_000]
print("block contains allow continuation:", 'if (line.includes("publishability-allow"))' in block)
print("block contains scan:", 'for (const { label, pattern } of DENYLIST)' in block)
allow_before_pattern = block.index('if (line.includes("publishability-allow"))') < block.index('for (const { label, pattern } of DENYLIST)')
print("allow exception occurs before denylist patterns are checked:", allow_before_pattern)
PY

Repository: arkorlab/haru

Length of output: 365


Tie publishability-allow to the exact collidee.

The scanner currently skips any line containing publishability-allow before checking the denylist. Document and test that the exemption only applies on the same line to a legitimate collision with a governed denylist token/rule, so unrelated prohibited references cannot bypass the gate.

🤖 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 `@AGENTS.md` around lines 223 - 224, The documentation and scanner behavior
around the publishability exemption must bind publishability-allow to the exact
legitimate collidee. Update the relevant denylist scanning logic and AGENTS.md
guidance so the marker is honored only when it appears on the same line as a
governed denylist token/rule collision, and add tests proving unrelated
prohibited references cannot bypass the gate.

Source: Coding guidelines

Comment thread packages/db/src/publishability.test.ts Outdated
Comment on lines +160 to +165
for (const [index, line] of content.split("\n").entries()) {
// Escape hatch: a line with a legitimate token that collides with a
// denylist word opts out with a `publishability-allow` marker. None
// exist today.
if (line.includes("publishability-allow")) {
continue;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a direct allow-marker test.

The documented escape hatch skips a matching line, but no Vitest case verifies that marked content is suppressed while the equivalent unmarked content is reported. Add this regression case next to the matcher tests. As per coding guidelines: “Add a Vitest case next to changed code when introducing or modifying behavior.”

🤖 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/db/src/publishability.test.ts` around lines 160 - 165, Add a Vitest
regression case next to the publishability matcher tests that compares
equivalent marked and unmarked lines: assert the line containing the
`publishability-allow` marker produces no finding, while the unmarked version is
reported. Use the existing matcher/test helpers and preserve their established
assertion style.

Source: Coding guidelines

Comment on lines +14 to +16
expect(parsed.success).toBe(false);

const formatted = formatSeedError(parsed.error);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files matching seed-error.test.ts =="
fd -a 'seed-error\.test\.ts$' . || true

echo "== package/config files for TypeScript type-checking =="
git ls-files | rg '(^|/)(tsconfig.*\.json|tsconfig.*\.js|eslint\.config\.(ts|js|mjs|cjs)|oxlint\.config\.(ts|js)|package\.json)$' | head -200

echo "== inspect test file context =="
for f in $(fd 'seed-error\.test\.ts$' .); do
  echo "--- $f"
  wc -l "$f"
  cat -n "$f"
done

echo "== inspect seed-error implementation and formatSeedError =="
fd 'seed-error' . | while read -r f; do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,220p' "$f"
done

Repository: arkorlab/haru

Length of output: 4800


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== relevant tsconfig/build files =="
for f in tsconfig.base.json packages/db/package.json packages/db/tsconfig.json packages/db/tsconfig.build.json tsconfig.base.json; do
  if [ -f "$f" ]; then
    echo "--- $f"
    sed -n '1,220p' "$f"
  fi
done

echo "== scripts mentioning typecheck/compile/tsc =="
python3 - <<'PY'
import json, pathlib
for p in [pathlib.Path('.package.json')] + list(pathlib.Path('.').rglob('package.json')):
    try:
        data=json.loads(p.read_text())
    except Exception:
        continue
    scripts=data.get('scripts',{})
    if any(k in scripts for k in ['typecheck','compile','build','test']):
        print(f'--- {p}')
        for k,v in data.get('scripts',{}).items():
            if any(s in k.lower() or any(t in s.lower() for t in ['tsc','typecheck','compile','test','vitest']) for s in [k,v]):
                print(k,':',v)
PY

echo "== package deps typescript/zod/vitest availability =="
node - <<'JS'
const fs = require('fs');
const path = require.resolve('typescript', { paths: ['./packages/db','node_modules'] });
console.log('typescript-path:', path);
console.log('typescript-version:', require('typescript').version);
try {
  console.log('zod-path:', require.resolve('zod', { paths: ['./packages/db','node_modules'] }));
} catch(e) {
  console.log('zod-path:', '<not available>');
}
try {
  console.log('vitest-path:', require.resolve('vitest', { paths: ['./packages/db','node_modules'] }));
} catch(e) {
  console.log('vitest-path:', '<not available>');
}
JS

Repository: arkorlab/haru

Length of output: 5751


🌐 Web query:

Zod safeParse success false type narrowing parsed.error success discriminator

💡 Result:

The Zod safeParse method returns a discriminated union, which allows for robust type narrowing [1][2]. The object returned by safeParse contains a success property that serves as the discriminant [3][4]. To correctly narrow the type and access the error or data properties, you must check the value of success [3][1]. TypeScript's control flow analysis will then automatically narrow the object type, enabling safe access to the relevant properties [5]. Example of proper handling: const result = schema.safeParse(input); if (!result.success) { // TypeScript narrows result to ZodSafeParseError // You can safely access result.error here [3][1] console.error(result.error); } else { // TypeScript narrows result to ZodSafeParseSuccess // You can safely access result.data here [1][2] console.log(result.data); } Important Requirements: 1. TypeScript Configuration: This type narrowing behavior relies on TypeScript's strict mode (specifically strictNullChecks set to true) [3][5]. If you encounter issues where error is not accessible even after a !result.success check, ensure your project's tsconfig.json has strict mode enabled [3][5]. 2. Discriminated Union Design: Because safeParse returns a discriminated union, accessing result.error directly without first checking result.success (or !result.success) will result in a TypeScript error, as error does not exist on the success variant of the union [3][4]. Similarly, data does not exist on the error variant [6][4]. If you are using array methods like .filter(), the type narrowing might not persist automatically [7]. In those cases, you may need to use a type predicate to inform TypeScript about the narrowed type [7].

Citations:


🌐 Web query:

Vitest expect boolean type narrowing TypeScript

💡 Result:

Standard Vitest expect matchers, such as .toBeTruthy() or .toBeFalsy(), do not perform TypeScript type narrowing at compile time [1][2]. Because expect is designed for runtime assertions, it does not inherently communicate type information to the TypeScript compiler. To achieve type narrowing within your tests, use the following approaches: 1. Use expect.assert(booleanExpression) For runtime checks that also inform TypeScript's control flow analysis (type narrowing), use expect.assert(). It accepts any boolean expression and applies the same type narrowing that TypeScript's compiler would apply within an if branch [1][3]. This is the recommended method for narrowing types during test execution [4]. ts const value: string | null = getValue(); expect.assert(value !== null); // TypeScript now knows 'value' is string console.log(value.toUpperCase()); 2. Use expectTypeOf for Compile-Time Checks If your goal is to test that a type is narrowed correctly (rather than narrowing it for runtime test logic), use expectTypeOf inside a dedicated type test file (e.g., .test-d.ts) and run Vitest with the --typecheck flag [5][6][7]. ts import { expectTypeOf } from 'vitest'; // Assert that the type is correctly narrowed expectTypeOf(value).toEqualTypeOf<string>(); Note: Do not rely on runtime expect matchers to influence TypeScript types. If you require strict boolean checking to avoid type coercion issues in tests, prefer using .toBe(true) or .toBe(false) over .toBeTruthy() and .toBeFalsy() [8].

Citations:


Narrow the safeParse result before accessing error.

expect(parsed.success).toBe(false) is a runtime assertion and does not narrow TypeScript’s safeParse discriminated union, so parsed.error on Line 16 is still a type error.

Proposed fix
     expect(parsed.success).toBe(false);
+    if (parsed.success) {
+      throw new Error("expected invalid fixture");
+    }

     const formatted = formatSeedError(parsed.error);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
expect(parsed.success).toBe(false);
const formatted = formatSeedError(parsed.error);
expect(parsed.success).toBe(false);
if (parsed.success) {
throw new Error("expected invalid fixture");
}
const formatted = formatSeedError(parsed.error);
🤖 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/db/src/seed-error.test.ts` around lines 14 - 16, In the seed-error
test, narrow the safeParse result after the parsed.success assertion before
accessing parsed.error in the formatSeedError call. Add an explicit control-flow
check or assertion that establishes the failure branch, while preserving the
existing runtime expectation and error formatting behavior.

Comment thread packages/db/src/seed.ts
Comment on lines +66 to +67
console.error(formatSeedError(error));
process.exit(1);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== locate seed.ts =="
fd -a 'seed\.ts$' . || true

echo "== file outline =="
ast-grep outline packages/db/src/seed.ts --view expanded || true

echo "== relevant seed.ts lines =="
cat -n packages/db/src/seed.ts | sed -n '1,140p'

echo "== process.exitCode/process.exit usage in db =="
rg -n "process\.(exit|exitCode)|db\.close|disconnect|client\.close" packages/db/src packages/db/test 2>/dev/null | head -200 || true

echo "== package files for node/db drivers =="
for f in package.json packages/db/package.json; do
  if [ -f "$f" ]; then
    echo "--- $f"
    sed -n '1,220p' "$f"
  fi
done

Repository: arkorlab/haru

Length of output: 6407


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== read remaining seed.ts if any =="
wc -l packages/db/src/seed.ts
cat -n packages/db/src/seed.ts | sed -n '140,260p'

echo "== imports and db symbol usages =="
rg -n "from ['\"].*/db|DB|Database|HaruDatabase|postgres|pglite|pool|db\.|\.transaction\(" packages/db/src packages/db/test 2>/dev/null | head -200 || true

echo "== deterministic node pipe flush comparison for direct stderr + exit behavior (sandbox only) =="
node - <<'JS'
function run(n) {
  const p = require('child_process').spawn(process.execPath, ['-e', `
    const stderr = process.stderr;
    stderr._type = 'write';
    console.error('${n}: queued');
    process.stderr._type = 'none';
    ${n === 'exitCode' ? 'process.exitCode = 1' : 'process.exit(1)'}
  `], { stdio: ['ignore', 'ignore', 'pipe'] });
  let out = '';
  p.stderr.on('data', (chunk) => out += chunk);
  p.on('close', (code) => console.log(`${n}: childClose=${code}, stderrBytes=${out.length}, stderrText=${JSON.stringify(out.replace(/^.*?queued\n/g, ''))}`));
}
run('exit');
run('exitCode');
JS

Repository: arkorlab/haru

Length of output: 7311


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== deterministic stdout flush comparison for direct stdout + exit behavior =="
node - <<'JS'
function run(n) {
  const p = require('child_process').spawn(process.execPath, ['-e', `
    const stdout = process.stdout;
    console.log('${n}: queued');
    ${n === 'exitCode' ? 'process.exitCode = 1' : 'process.exit(1)'}
  `], { stdio: ['ignore', 'pipe', 'pipe'] });
  let out = '';
  p.stdout.on('data', (chunk) => out += chunk);
  p.on('close', (code) => console.log(`${n}: childClose=${code}, stdoutBytes=${out.length}, stdoutText=${JSON.stringify(out.replace(/^.*?queued\n/g, ''))}`));
}
run('exit');
run('exit');
run('exitCode');
run('exitCode');
JS

echo "== direct Node stderr write test under a pipe =="
node - <<'JS'
const p = require('child_process').spawn(process.execPath, ['-e', `
  const chunk = Buffer.alloc(128 * 1024, Buffer.from("stderr ", "utf8"));
  process.stderr.write(chunk);
  process.exit(1);
`], { stdio: ['ignore', 'ignore', 'pipe'] });
let bytes = 0;
p.stderr.on('data', (data) => bytes += data.length);
p.on('close', (code) => console.log(`stdoutBytes=${bytes}, childClose=${code}`));
JS

echo "== direct Node console.log write test under a pipe =="
node - <<'JS'
const p = require('child_process').spawn(process.execPath, ['-e', `
  const chunk = Buffer.alloc(128 * 1024, Buffer.from("stdout ", "utf8"));
  process.stdout.write(chunk);
  process.exit(1);
`], { stdio: ['ignore', 'pipe', 'pipe'] });
let bytes = 0;
p.stdout.on('data', (data) => bytes += data.length);
p.on('close', (code) => console.log(`stdoutBytes=${bytes}, childClose=${code}`));
JS

Repository: arkorlab/haru

Length of output: 606


Exit the script only after closing the database client.

seed.ts creates the DB client in main(), but the only errors after that point call process.exit(1) and can skip cleanup. Close the client before exiting, or otherwise ensure cleanup cannot be bypassed by an early process.exit().

🤖 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/db/src/seed.ts` around lines 66 - 67, Update main() error handling
so the database client is closed before any process.exit(1) call, including
errors occurring after client creation. Ensure cleanup runs reliably before
exiting while preserving the existing formatted error logging.

// `#`-prefixed line inside a fenced code block (e.g. a shell comment) is
// not a heading at all, so track fence state and ignore it.
let isInFence = false;
let isEnded = false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not suppress the empty-section error when a heading terminates the section.

If the section contains no variables and is followed by ###, ##, or #, isEnded is true, so this condition returns an empty set instead of throwing. Check documented.size === 0 unconditionally and add an empty-section fixture.

Proposed fix
-  let isEnded = false;
   const body = lines.slice(start + 1);
...
-      isEnded = true;
       break;
...
-  if (!isEnded && documented.size === 0) {
+  if (documented.size === 0) {

Also applies to: 55-55, 74-77

🤖 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 `@services/haru-server/src/environment-contract.test.ts` at line 41, Update the
empty-section validation in the relevant environment-contract test fixtures so
it checks documented.size === 0 regardless of isEnded, preserving the expected
error when a heading terminates an otherwise empty section. Add an empty-section
fixture covering termination by ###, ##, or # and apply the same correction to
the other referenced cases.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1c377e5bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +113 to +117
return (
name.endsWith(".json") &&
name !== "package.json" &&
!name.startsWith("tsconfig")
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scan non-policy text files

Because isScannable accepts only the listed source/document extensions and selected JSON, every .txt file is excluded rather than only the two policy files. If a future seed, layout, documentation, or other shipped text file contains a forbidden workload identifier, the new gate remains green, despite the repository contract making those two governed files the sole exception; exclude those exact paths and scan other text files.

AGENTS.md reference: AGENTS.md:L217-L224

Useful? React with 👍 / 👎.

Comment on lines +20 to +24
function readReadme(): string {
return readFileSync(
fileURLToPath(new URL("../../../README.md", import.meta.url)),
"utf8",
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the Japanese environment table

This drift gate reads only README.md, so adding or removing a server environment variable while updating the English table alone passes CI even if README.ja.md retains a stale contract. Since these documents are required to remain paired, run the same schema-key comparison against the Japanese environment table as well.

AGENTS.md reference: AGENTS.md:L272-L278

Useful? React with 👍 / 👎.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 7 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/db/src/publishability.test.ts">

<violation number="1" location="packages/db/src/publishability.test.ts:260">
P3: The newly extracted `violationsInText` function implements the `publishability-allow` escape hatch, but no test case verifies that a line containing this marker is actually suppressed while the same content without the marker is reported. Adding a direct regression case would pin this behavior and prevent silent breakage of the escape hatch.</violation>

<violation number="2" location="packages/db/src/publishability.test.ts:263">
P2: The publishability test now fails on its own source: the added `qwen` example in this comment is matched by the denylist, while the scanner recursively includes this `.ts` file. Rephrasing the example without a literal denylisted identifier keeps the per-branch explanation without making the gate fail.</violation>
</file>

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/db/src/publishability.test.ts Outdated
it("detects every sampled identifier (per-token coverage)", () => {
// The rules are large alternations, so "the pattern matches the policy
// text" stays true even when individual alternatives are deleted. Each
// sample pins ONE branch: drop `|\\bqwen\\b` and this fails, where the

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The publishability test now fails on its own source: the added qwen example in this comment is matched by the denylist, while the scanner recursively includes this .ts file. Rephrasing the example without a literal denylisted identifier keeps the per-branch explanation without making the gate fail.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/src/publishability.test.ts, line 263:

<comment>The publishability test now fails on its own source: the added `qwen` example in this comment is matched by the denylist, while the scanner recursively includes this `.ts` file. Rephrasing the example without a literal denylisted identifier keeps the per-branch explanation without making the gate fail.</comment>

<file context>
@@ -122,33 +205,106 @@ describe("publishability", () => {
+  it("detects every sampled identifier (per-token coverage)", () => {
+    // The rules are large alternations, so "the pattern matches the policy
+    // text" stays true even when individual alternatives are deleted. Each
+    // sample pins ONE branch: drop `|\\bqwen\\b` and this fails, where the
+    // per-rule check above would not.
+    const samples = parseSamples(readFileSync(SAMPLES_PATH, "utf8"));
</file context>
Suggested change
// sample pins ONE branch: drop `|\\bqwen\\b` and this fails, where the
// sample pins ONE branch: drop an individual alternative and this fails, where the

Comment thread packages/db/src/publishability.test.ts Outdated
expect(rule?.pattern.test('const a = "TEST-ACCEL";')).toBe(true);
});

it("detects every sampled identifier (per-token coverage)", () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The newly extracted violationsInText function implements the publishability-allow escape hatch, but no test case verifies that a line containing this marker is actually suppressed while the same content without the marker is reported. Adding a direct regression case would pin this behavior and prevent silent breakage of the escape hatch.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/src/publishability.test.ts, line 260:

<comment>The newly extracted `violationsInText` function implements the `publishability-allow` escape hatch, but no test case verifies that a line containing this marker is actually suppressed while the same content without the marker is reported. Adding a direct regression case would pin this behavior and prevent silent breakage of the escape hatch.</comment>

<file context>
@@ -122,33 +205,106 @@ describe("publishability", () => {
+    expect(rule?.pattern.test('const a = "TEST-ACCEL";')).toBe(true);
+  });
+
+  it("detects every sampled identifier (per-token coverage)", () => {
+    // The rules are large alternations, so "the pattern matches the policy
+    // text" stays true even when individual alternatives are deleted. Each
</file context>

Adversarial self-review of the gate rework found the gate could still be
weakened without any test noticing:

- The per-rule coverage check used `violation.includes(label)` on a
  formatted string, so an empty label (which the parser accepted) matched
  every violation, and a label that is a substring of another rule's rode
  on that rule's hits - either way a dead pattern looked covered. Return
  structured violations and compare labels exactly; reject an empty label
  at parse time.
- The sample floor (`> 20` against 41 samples) licensed deleting twenty
  branch/sample pairs before anything failed. Pin the exact count so a
  deletion fails loudly and an addition is a visible one-line diff.
- A comment in the gate's own source named a model family, which the rule
  it documents forbids everywhere except the two .txt data files; it
  escaped detection only because a regex escape ate the word boundary.

All four review findings were reproduced before fixing.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 992f92432d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"dist",
"coverage",
".turbo",
"drizzle",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include committed migrations in the publishability scan

When a committed migration contains a forbidden identifier, this directory-level exclusion drops the entire packages/db/drizzle tree before extension filtering, so the gate remains green even though those SQL files ship and are replayed by deployments and tests. Scan .sql migrations and reserve exclusions for the two explicitly sanctioned policy data files.

AGENTS.md reference: AGENTS.md:L217-L222

Useful? React with 👍 / 👎.

@Nicolas0315

Copy link
Copy Markdown

Read this PR locally and worked through the 12 unresolved threads. Nine sit on publishability.test.ts, so here is a triage: three are real and share one root cause, one is stale, the rest are style. Verified each against 992f924.

Real, and all one bug: the gate's scope is narrower than the rule it enforces

AGENTS.md states the contract precisely - the gate scans "the packages/services/.github trees plus the root-level files", and its "ONE sanctioned exception" is the governed policy pair. The implementation has four exclusions, not one:

  1. .txt is excluded by extension in isScannable, so an exemption intended for two governed files covers every present and future .txt under a scan root. The doc comment gives the reason as "the governed home for the policy and its samples" - an argument true of exactly those two files and nothing else.
  2. drizzle is in SKIP_DIRECTORIES, so committed .sql migrations are never read even though they ship and are replayed by deploys and by the PGlite harness. .sql is also absent from the extension lists, so it is doubly excluded.

The consequence is the one outcome parseDenylist's own comment forbids: a gate that is green while not enforcing over files that reach consumers. Those are the chatgpt-codex-connector threads "Scan non-policy text files" and "Include committed migrations in the publishability scan". Both correct.

Suggested fix, verified locally: make .sql and .txt scannable, drop drizzle from SKIP_DIRECTORIES, and move the exemption to an exact repo-relative path set so it cannot widen silently. Today the only .txt under the scan roots are the two policy files, so this is a no-op on the current tree and a real gate from the next shipped file onward. The publishability-allow marker already covers a legitimate future collision.

Checked in both directions: with the patch, planting H100 in packages/db/drizzle/9999_canary.sql and H200 in a new packages/db/src/*.txt fails the gate with both violations reported by file:line:label:match; removing them returns it to green. Without the patch, both canaries pass unnoticed. pnpm --filter @haru/db test is 55/55 and the package lints clean with it applied.

The self-assertion test needed updating alongside, and the replacement is stronger: instead of asserting isScannable("publishability-denylist.txt") === false (which pins an extension-level exemption), it asserts the exempt set is exactly those two paths and that their extension is otherwise scanned.

Also real: environment-contract.test.ts reads only README.md

"Validate the Japanese environment table" is correct and is the same class - a drift gate scoped narrower than the contract it enforces. AGENTS.md requires EN/JA pairs, so adding or removing a server env var while touching only the English table passes CI with a stale README.ja.md.

Stale: the P1 "Remove hard-coded workload names from the gate"

That thread cites 0bbf5fa, before the denylist moved into publishability-denylist.txt. On 992f924 the gate file carries no literal GPU or model name (grep -nE 'H100|H200|A100|B200|MI300|llama|qwen|mistral|gemma|deepseek' over it returns nothing), which is what the P1 asked for. Safe to resolve as already addressed.

The rest

Severity-tagged style and coverage suggestions with no correctness consequence I could reproduce. Worth noting that all 23 line comments here are from bots, there is no human dissent, CI is green and the PR is MERGEABLE - so with the three items above addressed or consciously deferred to KNOWN_ISSUES, nothing else looks blocking.

Patch

diff --git a/packages/db/src/publishability.test.ts b/packages/db/src/publishability.test.ts
index 281f3b1..6ee3d47 100644
--- a/packages/db/src/publishability.test.ts
+++ b/packages/db/src/publishability.test.ts
@@ -89,7 +89,6 @@ const SKIP_DIRECTORIES = new Set([
   "dist",
   "coverage",
   ".turbo",
-  "drizzle",
 ]);
 // Every module extension the repo ships (nodenext ESM/CJS + the .mjs
 // generator scripts), so the gate governs ALL code, not only .ts.
@@ -99,12 +98,30 @@ const SOURCE_EXTENSIONS = [".ts", ".mts", ".cts", ".mjs", ".cjs", ".js"];
 // comments and docs, so they are governed too.
 const DOCUMENT_EXTENSIONS = [".md", ".yaml", ".yml"];
 
+// Committed migrations and governed text. Both SHIP: the migrations are
+// replayed by deploys and by the PGlite harness, and a `.txt` under a scan
+// root is shipped data like any other. Excluding them by extension (or by
+// dropping the whole `drizzle` tree) left the gate green over files that
+// reach consumers, which is the one outcome a guardrail must not have.
+const DATA_EXTENSIONS = [".json", ".sql", ".txt"];
+
 /**
- * Which files the gate reads. Deliberate exclusions, asserted below:
- * `package.json`/`tsconfig*.json` (build config, no workload data) and
- * `.txt` (the governed home for the policy and its samples - scanning
- * them would flag the policy by its own rules). Everything else with a
- * governed extension is in scope.
+ * The gate's ONE sanctioned exception, per AGENTS.md: the two governed
+ * policy DATA files it reads. Keyed by exact repo-relative path rather
+ * than by extension, so the exemption cannot silently widen to every
+ * future `.txt` - scanning these two WOULD flag the policy by its own
+ * rules, but that argument covers exactly these two files.
+ */
+const POLICY_DATA_FILES = new Set([
+  "packages/db/src/publishability-denylist.txt",
+  "packages/db/src/publishability-samples.txt",
+]);
+
+/**
+ * Which files the gate reads. `package.json`/`tsconfig*.json` carry build
+ * config and no workload data; everything else with a governed extension
+ * is in scope, including committed `.sql` migrations and shipped `.txt`.
+ * The policy pair is subtracted by path in `governedFiles`.
  */
 function isScannable(name: string): boolean {
   if (
@@ -114,10 +131,8 @@ function isScannable(name: string): boolean {
   ) {
     return true;
   }
-  // Shipped data (layouts, seeds, generated schemas), but not the
-  // build/config JSON, which carries no workload data.
   return (
-    name.endsWith(".json") &&
+    DATA_EXTENSIONS.some((extension) => name.endsWith(extension)) &&
     name !== "package.json" &&
     !name.startsWith("tsconfig")
   );
@@ -147,7 +162,7 @@ function governedFiles(): string[] {
   return [
     ...SCAN_ROOTS.flatMap((root) => scannableFiles(`${REPO_ROOT}${root}`)),
     ...rootLevel,
-  ];
+  ].filter((file) => !POLICY_DATA_FILES.has(file.slice(REPO_ROOT.length)));
 }
 
 interface Violation {
@@ -335,20 +350,32 @@ describe("publishability", () => {
       "a.yaml",
       "a.yml",
       "fleet.example.json",
+      "0000_migration.sql",
+      "shipped-data.txt",
     ]) {
       expect(isScannable(name), `${name} must be scanned`).toBe(true);
     }
-    // Deliberate exclusions: build config carries no workload data, and
-    // .txt is the sanctioned home for the policy and its samples.
+    // Deliberate exclusions: build config carries no workload data.
     for (const name of [
       "package.json",
       "tsconfig.json",
       "tsconfig.build.json",
-      "publishability-denylist.txt",
-      "publishability-samples.txt",
       "pnpm-lock.yaml.license",
     ]) {
       expect(isScannable(name), `${name} must be excluded`).toBe(false);
     }
+    // The policy pair is exempt BY PATH, not by extension: an
+    // extension-level exemption would silently cover every future .txt
+    // added under a scan root, which is how a guardrail goes quietly
+    // green. AGENTS.md sanctions exactly these two files.
+    expect(
+      [...POLICY_DATA_FILES].toSorted((a, b) => a.localeCompare(b)),
+    ).toEqual([
+      "packages/db/src/publishability-denylist.txt",
+      "packages/db/src/publishability-samples.txt",
+    ]);
+    for (const relative of POLICY_DATA_FILES) {
+      expect(isScannable(relative.split("/").at(-1) ?? "")).toBe(true);
+    }
   });
 });

Context: found haru through Hina's Zenn post and have been reading the control plane; #33 came out of the same pass. Happy to open this as its own PR against your branch instead if that is easier than a patch in a comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants