Skip to content

Commit 07e3674

Browse files
sahrizviclaude
andauthored
release: v0.9.6 (#1129)
* release: v0.9.6 Correctness release for `check --checks *` + native bridge resilience. Changelog: - altimate-core 0.7.0 upgrade + consumer contract sync surfaces findings many `check --checks {validate,semantic,grade,pii,migration,safety, policy,compare}` paths previously missed. Teams gating CI on `--fail-on error|warning` may see new failures on unchanged SQL — these are real findings, not regressions in your code. (#1090) - Truncated tool-output files were being deleted the moment they were written since 2026-08-14, due to a 48-bit Identifier timestamp wrap. Both cleanups now age files by mtime; stat failures fail safe (keep the file). (#1113, closes #1112) - Native bridge no longer poisons itself for the process lifetime on a transient NAPI load failure — registration now caches an in-flight promise, cleared on failure so subsequent calls can retry. Fix + adversarial tests from the v0.9.6 release review (Chaos Gremlin). Deferred to follow-up issues: #1124 (grace-window flag), #1125 (rule catalog docs), #1126 (legacy-shape-fallback removal), #1127 (NAPI-load- failure CI job), #1128 (truncate.ts/truncation.ts consolidation). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * release: v0.9.6 review — dispatcher race guard + qualify `rule` wording Addresses coderabbit findings on the release PR: - **Major** — dispatcher.ts: without generation tracking, a stale registration attempt's `.then` handler could clobber replacement state installed mid-flight by a concurrent `reset()` or `setRegistrationHook()`. Success path nulled `_ensureRegistered` (wiping a replacement hook); failure path nulled `_registrationPromise` (breaking dedup for a newer in-flight promise). Fix: bump a generation counter on every mutation entry point; the settle handlers only mutate cached state if their captured generation is still current. 2 new adversarial tests cover both races. - **Minor** — CHANGELOG.md and docs/docs/usage/check.md: the "every finding carries a `rule` field" claim isn't quite true — `lint` findings may omit `rule` if the engine didn't attach one (matches the documented Finding Object where `rule` is optional). Qualified the wording in both files to "when set" / "when the underlying engine attaches one" so users know to check for presence before switching on it. Local verification: `bun test test/skill/release-v0.9.6-adversarial.test.ts test/altimate/dispatcher.test.ts` → 14/14 pass (2 new race tests). `bun turbo typecheck` clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * release: v0.9.6 review round 2 — self-heal dispatcher after stale register() Addresses second round of coderabbit + cubic findings on release/v0.9.6: - **cubic P2 (dispatcher.ts)** — the previous generation guard prevented a stale hook's `.then` handler from mutating shared state, but the old hook BODY itself could still `Dispatcher.register()` late and clobber fresh entries a newer hook had already written. Fix: on stale-generation success, clear `_registrationPromise` so the next `Dispatcher.call` re-runs the CURRENT hook — its `register()` calls then idempotently overwrite whatever the stale hook wrote. Successful current-generation attempts keep the resolved promise memoized so subsequent calls fast-path through an already-settled await. - **cubic P3 (test file)** — replaced every `setTimeout` sync point with Promise-gate synchronisation. Bun's `async` function bodies run sync until the first `await`, so `Dispatcher.call(...)` has already registered its cached promise and hit `await _registrationPromise` by the time control returns to us — no external delay needed. Tests are now scheduler-independent. - **cubic P3 (docs/usage/check.md)** — rewrote the dangling "and how the rule inventory is discovered in practice" clause. Now links to the Finding Object schema and calls out which check types always vs sometimes include `rule`. - New adversarial test covering the stale-register self-heal path; full dispatcher suite: 15/15 pass. Non-vacuous: verified the new test FAILS if the P2 fix is reverted. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * release: v0.9.6 review round 3 — revert P2 self-heal; document contract Reverts the round-2 self-heal (was: on stale-generation success, clear `_registrationPromise` so the current hook re-runs). Coderabbit + cubic both correctly flagged that this reintroduces the very race the round-1 generation guard was meant to prevent: if the stale hook resolves while a REPLACEMENT hook is still in flight, clearing `_registrationPromise` clobbers the newer attempt's cached promise — a third caller then starts a second registration attempt, breaking dedup. Every attempt to self-heal without inventing a heavier per-entry generation scheme (or wrapping ``register()`` with a generation guard) introduces another race. Doing that here would materially complicate the dispatcher for a scenario that never occurs in production — ``setRegistrationHook`` is called exactly once at startup by ``native/index.ts``, and ``reset()`` is test-only. Test-authored races that violate isolation are the caller's contract, not this module's correctness problem. - Revert to round-1 logic (generation guard on shared-state mutations only) - Remove the "stale hook self-heal" adversarial test — it was locking in behavior we've decided not to guarantee - Add explicit contract documentation to `dispatcher.ts` and to the adversarial test file's top docstring so the design decision is discoverable to reviewers next time Dispatcher suite: 14/14 pass (was 15 with the deleted self-heal test). The round-1 generation guard is retained and still tested. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * release: v0.9.6 review round 4 — clarify dispatcher concurrency contract cubic P3 catch: my round-3 wording "reset()/setRegistrationHook() MUST NOT be called while a call is in flight" is contradicted by this same PR's adversarial tests — which do exactly that on purpose, to exercise the generation guard on the .then handlers. The wording was too broad. Rewrote to distinguish two claims: • What IS guaranteed: shared-state mutation (`_ensureRegistered` / `_registrationPromise`) by a stale attempt's .then handler is blocked by the generation guard. Concurrent reset/setRegistrationHook is safe wrt that. • What is NOT: late `Dispatcher.register(...)` calls from a stale hook BODY (that resumes after replacement) overwrite whatever the newer hook wrote. No self-heal — chased twice, recreated the guard's race both times. • Why: production never triggers late-write clobber (hook set once at startup, reset() test-only). Wording-only. No code change. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * release: v0.9.6 review round 5 — CHANGELOG + test-isolation fixes Addresses three cubic P2 findings on release/v0.9.6: - **CHANGELOG L10 + bullet (e)/(f)**: my theme + bullets listed `migration` and `compare` as valid `check --checks` lanes. They are not — `VALID_CHECKS` in `check-helpers.ts` only accepts {lint, validate, safety, policy, pii, semantic, grade}. The migration and compare correctness fixes belong to the `altimate-core-migration` and `altimate-core-compare` TOOLS (agent tools, not check lanes). Moved them to their own bullets and dropped them from the check-lane list in the theme. - **Test env-var teardown**: `afterAll` unconditionally deleted `ALTIMATE_TELEMETRY_DISABLED`, wiping any pre-existing value an outer suite may have set. Now captures the prior value in `beforeAll` and restores it (or deletes if none was set). - **Concurrent-failure test coverage**: added a companion to the concurrent-success test that fires 20 concurrent calls against a rejecting hook and asserts every one rejects with the SAME error instance (proving dedup held across the failure path) with the hook body having run exactly once. 15/15 dispatcher tests pass (14 prior + 1 new). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Haider <haider@altimate.ai> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent e27aeac commit 07e3674

4 files changed

Lines changed: 330 additions & 4 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.9.6] - 2026-08-23
9+
10+
Correctness release: `check --checks {validate,semantic,grade,pii,safety,policy}` and the `altimate-core-*` tools (`migration`, `compare`, `track-lineage`, `query-pii`, `classify-pii`) now tell you the truth. Several previously returned false-clean or wrong output — teams gating CI on `check --fail-on error|warning` may see new failures on unchanged SQL. **These are real findings the tool previously missed, not regressions in your code.** Also fixes a data-hygiene bug where truncated tool-output files were being deleted the moment they were written.
11+
12+
### Changed
13+
14+
- **`check --checks *` now surfaces the findings it was missing — expect newly-failing CI runs.** `altimate-core` upgrade `0.5.1 → 0.7.0` plus a consumer-contract sync catches a class of latent shape-mismatch bugs where the CLI was reading fields the engine no longer emits and rendering false-clean output as a result. Concretely: (a) `check --checks validate` gated on the wrong field and passed every file, now maps `ValidationError.location` and fails closed on engine failure; (b) `check --checks semantic` treated `valid:true` as clean, but `valid` means "plannable" — cartesian products came back "OK" for months, now reads `findings` and never gates on `valid`; (c) `check --checks grade` read fields `evaluate()` doesn't return, so no grade or finding ever surfaced, now reads `overall_grade`/`scores.overall`/`lint.findings` (with nested findings from `validation.errors` + `safety.threats`); (d) `check --checks pii` mapped the column name to the numeric column-position field and printed `[object Object]` for `{ Custom: string }` classifications, now reports the exposing alias and stringifies properly; (e) `check --checks policy` titled on `pass` (engine returns `allowed`), so clean SQL always rendered "VIOLATIONS FOUND", now inverted; (f) `check --checks safety` maps engine `high → error` and `medium → warning` (previously both degraded to `info`), so `--fail-on error|warning` no longer silently passes high-risk SQL injection threats; a new `unbalanced_quote` safety rule ships from the engine. Findings' `rule` field (when set — `lint` findings may omit it if the engine didn't attach one) names the rule in `--format json` output — that's how the safety and policy catalog is discovered in practice. (#1090)
15+
- **`altimate-core-migration` tool no longer renders "SAFE" for destructive migrations.** Previously read a non-existent `risks` field; every migration including `DROP COLUMN` came back safe. Now reads `findings`/`safe`/`overall_risk` (engine `MigrationResult`); never renders SAFE on engine error; counts only non-"safe" findings as risks. (#1090)
16+
- **`altimate-core-compare` tool no longer says "IDENTICAL" for different queries.** Read a non-existent `differences` field; different SQL always compared equal. Now reads the engine's `identical`/`diff_count`/`diffs` shape with an error-gated title. (#1090)
17+
- **`altimate-core-track-lineage` no longer returns "0 edges" for everything.** The tool read `edges` off the top-level result; the engine has always emitted them at `queries[].edges`, so lineage tracking silently produced nothing. Now collects correctly, renders `impact_map`, formats `{table, column}` refs, and renders ERROR instead of "0 edges" when the engine call itself fails. (#1090)
18+
- **`schema.detect_pii` returned zero findings for every scan.** Same shape-mismatch class: the detector read `piiData.findings`, but the engine's `PiiReport` is `{ columns, pii_count, … }`. Both cache and live paths now filter `classification !== "None"` through the shared `piiColumnsFromReport` helper. Malformed `PiiReport` shapes now throw rather than silently yield zero findings. Failed per-column classifications flip `success` to `false` — the schema-detect-pii tool renders "PII Scan: ERROR" with any partial findings attached, instead of a clean "no findings" verdict. (#1090)
19+
- **`altimate-core-{compare,column-lineage,extract-metadata,import-ddl}` no longer crash on the default invocation.** Empty-string dialects now coerce to `undefined` before hitting `Schema.fromDdl`, centralized as `dialectHint()` in `native/engine-coerce.ts` and applied across all 7 dialect-forwarding handlers. (#1090)
20+
21+
### Fixed
22+
23+
- **Truncated tool-output files were being deleted the moment they were written (since 2026-08-14).** `Identifier.create` packs `timestamp * 4096 + counter` into 6 bytes and wraps every ~795 days; the 26th wrap landed 2026-08-14T11:19:55Z. Both truncation cleanups (`tool/truncate.ts` Effect service and `tool/truncation.ts` legacy module — used by bootstrap, bash, prompt) computed a pre-wrap cutoff astronomically larger than every post-wrap file's decoded timestamp, so every truncated tool output written after Aug 14 was garbage-collected on the next cleanup pass. Both cleanups now age files by `mtime` (which doesn't wrap); `stat` failures keep the file (deletion fails safe, so a dangling symlink or transient FS error can't wipe real cached truncations). The Effect-service cleanup now `stat`s through the injected `FSUtil.Service` for consistency with every other operation. Tagged `upstream_fix` — the wrap-prone encoding is upstream OpenCode code. (#1113, closes #1112)
24+
- **Native bridge no longer poisons itself for the process lifetime on a transient NAPI load failure.** The lazy registration hook was nulled *before* the `await fn()` that loads all bridge handler modules; if the altimate-core NAPI binding failed to load mid-sequence (older glibc, unusual arch, permissions), every subsequent `Dispatcher.call` — not just the failing one — threw `No native handler for X` for the rest of the process, and the CLI had to be restarted. Registration is now cached as an in-flight promise: concurrent callers share one attempt, and on failure the cached promise is cleared so subsequent calls can retry. (v0.9.6 review)
25+
826
## [0.9.5] - 2026-08-10
927

1028
Windows `grep` back for the ~16% of Windows users it silently broke since v0.9.2, plus a mid-session YOLO toggle and a welcome panel that stops eating half of narrow terminals.

‎docs/docs/usage/check.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ altimate-code check --fail-on warning
5050
| `semantic` | Semantic validation (cartesian products, wrong JOINs) | Optional | No |
5151
| `grade` | SQL quality grading with recommendations | Optional | No |
5252

53+
Findings usually carry a machine-readable `rule` field (e.g. `unbalanced_quote`, `sql_injection`, `L003`) visible in `--format json` output — see the [Finding Object](#finding-object) schema below. `safety` and `policy` findings always include one; `lint` findings may omit both `rule` and `code` if the engine didn't attach them, so switch on presence rather than assuming.
54+
5355
By default, `lint` and `safety` are enabled. Override with `--checks`:
5456

5557
```bash

‎packages/opencode/src/altimate/native/dispatcher.ts‎

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,84 @@ export function register(method: BridgeMethod, handler: NativeHandler): void {
2020
/** Lazy registration hook — set by native/index.ts */
2121
let _ensureRegistered: (() => Promise<void>) | null = null
2222

23+
/** In-flight registration promise (deduped across concurrent callers). */
24+
let _registrationPromise: Promise<void> | null = null
25+
26+
/** Generation counter — bumped whenever the hook or in-flight promise is
27+
* replaced. An in-flight attempt captures its generation at start; if the
28+
* counter advanced by the time its settle handler fires, another caller
29+
* (reset / setRegistrationHook / a distinct new attempt after failure)
30+
* has already installed replacement state, and the stale attempt must NOT
31+
* mutate it. Prevents a stale success from clobbering a replacement hook,
32+
* and a stale failure from clobbering a newer in-flight promise. */
33+
let _registrationGeneration = 0
34+
2335
/** Clear all registered handlers and lazy registration hook (for test isolation). */
2436
export function reset(): void {
2537
nativeHandlers.clear()
2638
_ensureRegistered = null
39+
_registrationPromise = null
40+
_registrationGeneration++
2741
}
2842

2943
/** Called by native/index.ts to set the lazy registration function. */
3044
export function setRegistrationHook(fn: () => Promise<void>): void {
3145
_ensureRegistered = fn
46+
_registrationPromise = null
47+
_registrationGeneration++
3248
}
3349

3450
/** Dispatch a method call to the registered native handler. */
3551
export async function call<M extends BridgeMethod>(
3652
method: M,
3753
params: (typeof BridgeMethods)[M] extends { params: infer P } ? P : never,
3854
): Promise<(typeof BridgeMethods)[M] extends { result: infer R } ? R : never> {
39-
// Lazy registration: load all handler modules on first call
55+
// Lazy registration: load all handler modules on first call. Cache the
56+
// in-flight promise so concurrent callers share one attempt; on failure
57+
// clear the cached promise so a subsequent call can retry. Previously
58+
// ``_ensureRegistered`` was nulled BEFORE the await, so a transient NAPI
59+
// load failure poisoned the bridge for the process lifetime — every
60+
// subsequent ``call`` threw ``No native handler for X`` with no way to
61+
// recover without restarting the CLI. Generation guard prevents a stale
62+
// attempt from mutating state a concurrent ``reset()``/``setRegistrationHook()``
63+
// has since replaced. (coderabbit round 1 — release/v0.9.6 review.)
64+
// Concurrency contract:
65+
// • ``reset()`` / ``setRegistrationHook()`` MAY be called while an
66+
// older ``Dispatcher.call`` is in flight — the generation guard below
67+
// blocks the stale attempt's ``.then`` handler from mutating shared
68+
// state (``_ensureRegistered`` / ``_registrationPromise``) that the
69+
// replacement installed. Adversarial tests below exercise both races.
70+
// • What we DO NOT guarantee: if the stale hook body itself resumes
71+
// after replacement and calls ``Dispatcher.register(...)`` late,
72+
// that late write overwrites whatever the newer hook wrote — and
73+
// no in-band signal lets us self-heal it without recreating the
74+
// shared-state race the round-1 guard is meant to prevent (see the
75+
// coderabbit + cubic round-2 exchange on release/v0.9.6). Callers
76+
// that need late-write safety must serialise hook mutations
77+
// against outstanding calls themselves.
78+
// • Production never triggers late-write clobber: ``setRegistrationHook``
79+
// is called exactly once at startup by ``native/index.ts``, and
80+
// ``reset()`` is test-only.
4081
if (_ensureRegistered) {
41-
const fn = _ensureRegistered
42-
_ensureRegistered = null
43-
await fn()
82+
if (!_registrationPromise) {
83+
const fn = _ensureRegistered
84+
const generation = ++_registrationGeneration
85+
_registrationPromise = fn().then(
86+
() => {
87+
// Only clear _ensureRegistered if our generation is still current
88+
// — otherwise a concurrent reset()/setRegistrationHook() already
89+
// installed a replacement, and clearing would clobber it.
90+
if (generation === _registrationGeneration) _ensureRegistered = null
91+
},
92+
(err) => {
93+
// Same guard on the failure path: don't null a newer in-flight
94+
// promise from another attempt.
95+
if (generation === _registrationGeneration) _registrationPromise = null
96+
throw err
97+
},
98+
)
99+
}
100+
await _registrationPromise
44101
}
45102

46103
const native = nativeHandlers.get(method as string)

0 commit comments

Comments
 (0)