Commit graph

1284 commits

Author SHA1 Message Date
Resham Joshi
de536cf55c
Merge pull request #1074 from getagentseal/fix/core-model-bound-guard-landing
fix(core): bound the model field at the observation boundary, and make the privacy guards prove it
2026-08-21 11:03:59 -07:00
iamtoruk
895639ee1f test(cli): pin the privacy-key rotation end to end; harden the key guard
Three review follow-ups.

1. The rotation guarantee is now pinned by a real parse, not by two unit
tests that both stub the fingerprint. The original defect was a wiring
bug — right value, wrong selector — and a stubbed fingerprint cannot see
a wiring bug. The new case warms the cache under key K1 (one JetBrains
turn, one call), rotates the key file to K2, appends a second turn, and
re-parses in a fresh module registry: exactly two calls, no K1-era dedup
key anywhere, on disk or in the result. Restore the substring-sniff
selector and it reports three calls for two turns — the user-visible
shape of the bug.

2. The test-helper guard compared UNRESOLVED paths, so a symlink at a
sandbox-shaped location (HOME=$TMPDIR/codeburn-test-env-x -> /Users/me)
passed the prefix test and the write landed in the real home anyway. Both
sides are realpath'd before comparing, which also stops a sibling
directory sharing the tmpdir prefix from matching. Same refusal message.

3. CHANGELOG records the two API breaks that were missing: decodeCopilot
throws on an empty privacyKey for JetBrains records, and
SOURCE_REF_KEYED_PARSE_VERSION is no longer exported (KEY_DERIVED_PROVIDERS
replaces it).
2026-08-21 10:59:50 -07:00
iamtoruk
e7c6dc1560 test(cli): refuse to overwrite a real privacy key from the test helper
fixed-privacy-key.ts writes <home>/.config/codeburn/privacy-key
unconditionally. Under vitest that is the throwaway sandbox
env-isolation.ts mints, and harmless. Imported from anywhere else — tsx,
a stray node -e, a REPL — homedir() is the developer's real home and the
write silently replaces their key, re-keying every resource fingerprint
and orphaning whatever was already synced. privacy-key.ts refuses to
cause that on its own; a test helper must not cause it either.

The sandbox has a recognizable shape (a mkdtemp dir under tmpdir() named
codeburn-test-env-*), so the guard needs no new marker: anything else
throws with the reason.
2026-08-21 10:48:21 -07:00
iamtoruk
0d7dbd81d2 fix(core): reject an empty privacy key in copilot's JetBrains digest
createHmac accepts '' happily and returns a digest anyone can recompute
from the reply text — exactly the weakness the HMAC replaced. Every
other keyed primitive in the codebase throws instead of degrading
(fingerprint.ts's hmac, sanitizeDetail); this one now matches, with the
same message.
2026-08-21 10:48:21 -07:00
iamtoruk
9ec98d2f96 fix(core): stop normalizeModelIdentifier throwing on non-string input
It called raw.trim() unconditionally. Two decoders resolve their model
through a plain-object lookup — warp's modelAliases[model],
antigravity's modelMap[usage.model] — so a session whose model names a
prototype member ('__proto__', 'constructor', 'toString', 'valueOf')
gets an object or a function back off the prototype chain, and .trim()
on that is a TypeError that takes the whole parse down with one hostile
record.

That is a regression against what this replaced: `z.string()` rejected a
non-string gracefully. Anything that is not a string is simply not an
identifier, so it collapses to 'unknown' like every other
non-conforming value.

Guarded at the shared boundary rather than at the two lookups: all 34
producers route through here, so one guard covers the sites that exist
and any lookup added later.

Tests drive the four reachable prototype names end to end through the
real warp decoder (non-vacuously: the decode must still produce calls),
plus the unit-level statement for every type a lookup can return.
2026-08-21 10:48:21 -07:00
iamtoruk
52e6678cd3 fix(cli): select the privacy-key fold set explicitly, not by substring
computeEnvFingerprint decided which providers re-parse on a privacy-key
change by sniffing 'source-ref-fingerprint-v1' out of the parse-version
string. That silently missed copilot. Copilot's dedup keys are just as
key-derived — createHmac(privacyKey) over the JetBrains reply text — but
its parse version reads '…-dedup-key-hmac-v1', so its fingerprint held
still across a key rotation.

Copilot is the sole DURABLE provider: its union-merge never deletes
cached turns, it appends any turn whose key is not already cached. A
fingerprint that does not move therefore does not merely re-parse — it
keeps the old key AND appends the new one for the same turn. Two real
turns became three calls. And a corrupt or unreadable key file yields a
fresh ephemeral key in EVERY process, so that inflation compounded on
every run.

KEY_DERIVED_PROVIDERS lists the seven explicitly. A provider is in the
set because of what its decoder does, which no substring of a version
label can know. The fold also moves out from under `if (parseVersion)`:
the key dependency comes from the decoder, not from having a parse
version entry.

The bridge comment claimed the fold covered "the affected providers" and
that the ephemeral-key fallback only happens when the cache cannot
persist. Both were false — the config dir (~/.config/codeburn) and the
cache dir (~/.cache/codeburn, or CODEBURN_CACHE_DIR) are different,
separately overridable directories, so a writable cache plus an
unreadable key file means a new key every process. Corrected.

Tests: the rotation pair now covers all seven key-derived providers and
five unaffected ones, plus a dedicated copilot case that pins the exact
regression (parse version has no source-ref token, provider is durable,
fingerprint must still move).
2026-08-21 10:48:21 -07:00
iamtoruk
5c4a8a5299 chore: hold workspace versions at 0.9.20, defer the bump to release
Maintainer call: the diagnostics/observation breaking changes are
documented in the CHANGELOG now, but the version number moves at the
next actual publish (core before cli), not on the branch. The 0.x bump
owed for the core API break is recorded in the CHANGELOG heading.
2026-08-21 10:38:17 -07:00
Resham Joshi
26342e796a
Merge pull request #937 from ozymandiashh/fix/port-misc
fix: recover a corrupt refresh lock instead of freezing ingestion
2026-08-21 10:37:22 -07:00
iamtoruk
0fee325b24 Merge branch 'feat/core-extraction' into fix/port-misc
Conflict resolution rules applied:

- packages/cli/src/daily-cache.ts (the only textual conflict): kept the BASE
  side of the version constants and their coordination comment verbatim
  (DAILY_CACHE_VERSION / MIN_SUPPORTED_VERSION stay at 26 — this change moves
  when the watermark advances, not what any cached day contains, so no
  re-derive is owed). Kept the BASE widened re-derive window
  (DAILY_CACHE_RETENTION_DAYS, with its straddle/tz comment) and layered this
  branch's completeness logic on top of it: capture the parse result
  (freshProjects) so sessionComplete() can read the tag off the exact array,
  priorWatermark hold-back on a partial parse, complete: parseWasComplete, and
  the watermarkTrusted stamp.

- packages/cli/src/parser.ts: auto-merged; #929's per-call slicing and #930's
  discovery changes are intact, and this branch's read-only-path additions
  (readOnlyServedStale on the stale/missing-cache-entry arms, network providers
  served from cache instead of re-fetched, hydration completeness tagged onto
  the result array) attach to the post-#929 code paths. Re-read end to end
  after the merge.

- packages/cli/tests/cache-refresh-lock*.test.ts, vitest.config.ts,
  package.json: kept the base config values (testTimeout 30s, retry 2, the
  lock-quarantine test/test:locks scripts, #1068's inode fix, #921's retries)
  and added this branch's corrupt-lock arms on top. The new describe's retry
  is 3, not 6, per review. Added the new corrupt-body file to test:locks:
  the base `test` script excludes tests/cache-refresh-lock*, so the new file
  would otherwise run in no suite.

- Everything else auto-merged as a union; no hunk from either side dropped.
2026-08-21 10:34:13 -07:00
Resham Joshi
fbef849010
Merge pull request #934 from ozymandiashh/fix/port-kiro
fix(kiro): estimate chat-file input tokens from every human turn, not a 500-char slice
2026-08-21 10:27:06 -07:00
iamtoruk
916f32530e Merge origin/feat/core-extraction into fix/port-kiro
Resolves daily-cache version coordination: base landed v25 (#930); this
PR's kiro chat-file fix takes the next number, v26, since it changes
historical kiro chat-file numbers and needs its own one-time
re-derivation. Session-cache kiro parse fingerprint bump (v1 -> v2) is
kept alongside base's opencode fingerprint addition. CHANGELOG entries
combined (keep-both), with the kiro entry's cache-version reference
updated to v26.
2026-08-21 10:21:52 -07:00
iamtoruk
e1234b0312 chore: 0.10.0 and changelog for the observation-boundary hardening
@codeburn/core takes a minor bump under 0.x semver: the diagnostics API
is a breaking change (DIAGNOSTIC_DETAIL_MAX deleted, sanitizeDetail
signature, DiagnosticDetail retightened) and observation-0.2.0.json is
tightened in place, so an archived pre-hardening envelope whose model
held a display name no longer validates against that same version string.

check-workspace-versions requires root, cli and core to move together, so
all three (and package-lock.json) go to 0.10.0. Publish order at the next
publish is core before cli, as always.
2026-08-21 10:18:58 -07:00
iamtoruk
70b6bd9c9d test(core): make the privacy guardrails prove what they claim
The smuggling guardrail asserted a planted prompt DID survive into the
envelope ("identifier-exemption convention") — an inverted assertion
certifying the hole as correct. It is gone. In its place: the model field
is asserted clean, a positive control proves a legitimate identifier
still crosses unchanged, and every hostile case asserts non-vacuously
(the decode really produced calls) before asserting secret-freedom.

Where a secret still ships, it says so instead of hiding it.
`serializeWithoutDedupKeys` excludes exactly one field, names why
(dedup-key hashing is the schema-wide follow-up), and is used only where
a planted value rides a key component — so the exclusion is visible in
the diff rather than buried in a passing assertion.

New end-to-end coverage for the six source-ref providers: the raw path
appears nowhere in the envelope and each key matches its fingerprint
shape.

import-smoke additionally calls one representative parser, decoder and
detector body under the blocked-I/O preload, and the banned-module sanity
check now covers every entry in the blocklist rather than fs alone. Its
docstring states the scope honestly: import purity for every subpath,
call-time purity for three representatives — a spot check, not a sweep.

The architecture gate walks the published schemas structurally, so a
loosened constraint fails the gate instead of passing unnoticed, and its
synthetic self-test proves the walker can actually detect one.
2026-08-21 10:18:58 -07:00
iamtoruk
fb433df656 fix(core): key the diagnostic detail and copilot's JetBrains digest
A diagnostic detail was "any string without a path separator, capped at
200 chars". That let a path with no slashes, a command fragment, a prompt
line, or an API key through verbatim — the rule was structural but it
guarded the wrong structure. A detail is now the 16-hex HMAC-SHA256
fingerprint of the offending input under the host privacy key: identical
failures dedupe, distinct failures differ, and no substring of the input
survives. A caller with no key omits the field entirely rather than
emitting an unkeyed digest, which is what `keyedDetail` is for.

isolateRecords is the only place a detail is derived. Diagnostics a
caller RETURNS are trusted for index and code only; any detail they carry
is stripped, so an unkeyed or caller-invented digest cannot cross the
boundary through a loose cast. The three decoders that reported
malformed-json (opencode-session, vscode-cline, zed) are wired to it.

Copilot's JetBrains dedup key embedded an unkeyed sha256 of the
assistant's REPLY TEXT — dictionary-attackable for short replies ("OK",
"Done.") — and it crosses into the envelope and the CLI ledger. It is now
an HMAC under the host privacy key, with a
`cli-shutdown-cost-v1-skills-dedup-key-hmac-v1` parse version: copilot is
the sole durable provider, so its union-merge would otherwise keep the
old-shape keys and append the new ones for the same records.

Breaking for consumers: DIAGNOSTIC_DETAIL_MAX is gone, sanitizeDetail
takes a key, DiagnosticDetail accepts only the fingerprint, and
RecordOutcome.diagnostics may no longer carry a detail.
2026-08-21 10:18:58 -07:00
iamtoruk
a4be04d926 fix(core,cli): fingerprint the source path out of dedup keys
dedupKey is a CallObservation field: it ships on the envelope. Six
decoders — codebuff, zerostack, pi, omp, grok and lingtai-tui — were
folding the absolute source path (a chat directory, a session file) into
it raw, so a host path rode every payload derived from those sessions.

They now fold `sourceRefFingerprint`, a keyed HMAC-SHA256 under a new
`source` domain. Like every other fingerprint in that module the key is
required and an empty key throws, so a source ref can never degrade to an
unkeyed, dictionary-attackable digest.

That means the CLI bridge has to supply a real key. It passed `''`, which
was correct when minimization happened only on the sync path; it now
threads getHostPrivacyKey() — per-install stable, so dedup keys stay
stable across runs.

The keys change VALUE, so the six carry a `source-ref-fingerprint-v1`
parse version that forces one re-parse and drops the cached raw-path keys
instead of re-ingesting the same records under two shapes. A parse
version cannot see the privacy key change, though, and a lost, rotated,
or ephemeral (unwritable config dir) key would silently produce keys that
never match the cached ones — so computeEnvFingerprint folds a digest of
the key in for exactly those providers.

The five bridge parity goldens used to compute their expected keys with
the same function and key production uses, which pins nothing. They now
re-derive the fingerprint longhand under a pinned test key, assert the key
SHAPE, and assert the raw fixture path appears nowhere in it.
2026-08-21 10:18:58 -07:00
iamtoruk
cff80ccdb1 fix(core): bound the model field at the observation boundary
`model` on a CallObservation was `z.string().min(1)` — unbounded free text
on a field that ships in the envelope. Bound it (and the optional
`pricingModel`) to the identifier charset real model slugs use,
`[A-Za-z0-9._:/@-]`, max 128, in both the zod schema and the published
schemas/observation-0.2.0.json.

Providers report display names ("Gemini 3.5 Flash (High)"), so a hard
reject would fail a whole multi-session batch over one call. Every
provider's toObservations now normalizes through
`normalizeModelIdentifier`: conforming values pass through untouched,
everything else collapses to 'unknown' — the same fallback the decoders
already use when no model resolves.

BOUNDARY ONLY. The normalizer is never called while building a dedup key:
a key is an identity, not an observation field, and collapsing two
distinct raw models to 'unknown' inside one would merge two real calls
and re-key every cached key for a partial win. The key's free-text
surface is schema-wide (every provider's key carries raw components) and
belongs to the dedup-key-hashing follow-up.

CLI output is unaffected — normalization happens at the observation
boundary only, so no terminal, dashboard, menubar or desktop number or
label moves.
2026-08-21 10:18:58 -07:00
Resham Joshi
86a6a549e6
Merge pull request #942 from ozymandiashh/fix/port-env-fingerprints
fix(cache): declare the provider env overrides on the extraction layout
2026-08-21 10:05:31 -07:00
iamtoruk
63196ed449 merge feat/core-extraction: keep both CHANGELOG entry sets 2026-08-21 10:01:49 -07:00
Resham Joshi
de5f76d2b3
Merge pull request #936 from ozymandiashh/fix/qwen-dedup
test(qwen): pin the orphaned-record dedup key shape
2026-08-21 10:01:25 -07:00
Resham Joshi
35b3400d61
Merge pull request #932 from ozymandiashh/fix/port-sync
feat(sync): push git attribution spans, with the hardening that followed
2026-08-21 09:55:58 -07:00
iamtoruk
11bd721c01 test(sync): assert repo path and commit subject never hit the wire
Strengthens the --attribution wire assertions to confirm the local
repo directory and a fixture commit subject never appear in the raw
OTLP payload, not just that known-sensitive markers are absent.
2026-08-21 09:32:11 -07:00
iamtoruk
bdbbf83004 docs(changelog): document sync id re-key and hard-abort behavior
codeburn sync now derives device/span/trace ids as domain-separated
HMAC digests under the per-install privacy key instead of bare hashes,
a one-time identity re-key on upgrade. sync push also hard-aborts on
an unwritable config dir or a corrupt privacy-key file rather than
falling back to an unstable ephemeral key.
2026-08-21 09:32:11 -07:00
iamtoruk
9045f75f8e Merge remote-tracking branch 'origin/feat/core-extraction' into pr932-fix 2026-08-21 09:30:16 -07:00
Resham Joshi
73d255b6b0
Merge pull request #930 from ozymandiashh/fix/port-providers
fix(cli): restore pi/omp, cline and opencode session discovery
2026-08-21 09:28:57 -07:00
iamtoruk
aaa73da654 merge feat/core-extraction (#929 landed), re-bump daily-cache to 25
#929 merged mid-task and claimed DAILY_CACHE_VERSION 24 for the codex
structural-discovery + midnight-straddle fixes. This PR's pi/omp/cline/
opencode discovery fix now takes 25, the next free number, so it forces
its own one-time re-derivation on top of #929's. Test literal updated to
match (pre-fix pinned at 24, the base's actual value before this bump).
2026-08-21 09:22:37 -07:00
iamtoruk
789fb53cb5 test(session-cache): cover opencode/kilo-code parser-version bumps
No test exercised the reparse gate for the PROVIDER_PARSE_VERSIONS entries
this PR bumps. Reproduce each provider's pre-bump fingerprint and assert it
now differs, proving a cache keyed under the old fingerprint is treated as
stale.
2026-08-21 09:21:39 -07:00
iamtoruk
cc774f812a test(daily-cache): derive PRE_FIX_CACHE_VERSION from the actual base version
The seeded old-cache version was a guessed 16 that was never this branch's
real pre-fix version, so the re-derivation test passed even with the
version bump reverted. Pin it to 23 (the base's DAILY_CACHE_VERSION right
before this PR's bump), verified to fail when the bump is reverted.
2026-08-21 09:21:34 -07:00
Resham Joshi
142ee4dbfd
Merge pull request #929 from ozymandiashh/fix/port-parser
fix(parser): keep both halves of a midnight-straddling turn, and stop --provider leaking claude
2026-08-21 09:19:48 -07:00
Resham Joshi
2a3c5a190e
Merge pull request #1068 from getagentseal/fix/inode-test-reuse
test: guarantee a distinct inode in the file-replaced edge case
2026-08-21 09:16:55 -07:00
iamtoruk
0e2f75ab86 merge feat/core-extraction into pr930-fix, resolve daily-cache version conflict
Base moved to DAILY_CACHE_VERSION 23; this PR's discovery fix now takes 24
to force its own one-time re-derivation on top of the codex structural-
discovery bump. CHANGELOG keeps both entries.
2026-08-21 09:16:17 -07:00
iamtoruk
1e1b3635b9 test: guarantee a distinct inode in the file-replaced edge case
unlink-then-recreate at the same path lets ext4 hand the freed inode
straight back, which CI runners reproduce reliably; the assertion then
fails as 'expected N not to be N'. Write the replacement beside the
original while the old inode is still allocated, then rename over it.
2026-08-21 09:14:31 -07:00
iamtoruk
0e591d66aa revert(main): drop the buildJsonReport dailyMap hunk — the fallback is unreachable (both call sites always pass durable); leave dead code untouched, file follow-up instead 2026-08-21 09:09:27 -07:00
iamtoruk
90e6c8b67a test(parser): pin the re-anchor case on the claude path, add the straddle report test
The "anchor sits before the range" test ran on the codex path, where the
turn anchor is derived from the first call — the scenario it describes cannot
occur there, so it passed with parser.ts fully reverted. Moved to the claude
path, where turn.timestamp is the user-message time and can precede the first
assistant call. Verified: fails on pre-fix parser.ts with the 23:57 anchor,
passes with the fix.

Added a `report --format json` test for a turn whose two calls straddle local
midnight, asserting both days appear with a one-call-each split and that the
per-day costs still sum to the headline.

Also drop the `?? classifiedFull` fallback in both parseProviderSources loops.
turnSlicedToRange has already proved a call is in range over the same call
list, so the fallback is unreachable — and if it were ever reached it would
silently restore the whole turn and reintroduce the straddle bug rather than
fail loudly.
2026-08-21 09:06:06 -07:00
iamtoruk
08922bdc74 Merge branch 'feat/core-extraction' into fix/port-parser
Conflict: packages/cli/src/daily-cache.ts. #926 landed
DAILY_CACHE_VERSION/MIN_SUPPORTED_VERSION at 23 for the structural codex
discovery re-derivation; this branch had claimed 17 for the midnight-straddle
re-derivation. Kept #926's rationale as the foundation and took 24, the next
free number, with the straddle reason appended: per-call day slicing changes
which day a historical straddling call lands on, so it needs its own one-time
re-derivation and cannot ride on a 23 cache written by a #926 binary.
2026-08-21 09:05:52 -07:00
Resham Joshi
41b1b3a033
Merge pull request #926 from ozymandiashh/fix/port-codex
fix(codex): validate rollouts structurally, guard the parse path
2026-08-21 08:55:45 -07:00
iamtoruk
34d50e1185 fix(cache): bump DAILY_CACHE_VERSION to 23, not 16
Base has moved since this branch opened: main shipped 17 in v0.9.20 and
now sits at 20, with 21 (#946) and 22 (#1056) claimed on the main-side
pipeline. Bumping to only 16 would let a main-built cache pass the
version check unchanged, so the widened Codex discovery re-derivation
this PR depends on would never fire for those users. Take 23 to stay
above every value a real cache file can carry on either line of
history, and update the test literal and MIN_SUPPORTED_VERSION to
match.

Also documents, next to CODEX_CACHE_VERSION, why that constant is
deliberately not bumped alongside it: the guarded record shapes were
measured at 0 occurrences across 136k real events, so forcing a full
re-parse of multi-GB rollout corpora for them is a bad trade. The
daily-cache bump alone already propagates the discovery widening.
2026-08-21 08:51:27 -07:00
iamtoruk
dc630a4c94 Merge remote-tracking branch 'origin/feat/core-extraction' into pr926-fix 2026-08-21 08:47:57 -07:00
Resham Joshi
e604f58f44
Merge pull request #925 from ozymandiashh/fix/junk-reads
fix(optimize): one junk vocabulary for the count, the trend and the display
2026-08-21 08:41:52 -07:00
Resham Joshi
aba8ed547d
Merge pull request #924 from ozymandiashh/fix/core-package-hygiene
build(core): stop shipping sourcemaps, expose schemas, tighten the barrel
2026-08-21 08:37:42 -07:00
Resham Joshi
36c3b502ba
Merge pull request #923 from ozymandiashh/fix/ci-run-cli-suite
ci: run the CLI suite
2026-08-21 08:36:40 -07:00
iamtoruk
734e53b6b6 merge: take base's cli test scripts (vitest run + lock quarantine supersede the watch-mode fix) 2026-08-21 08:33:46 -07:00
Resham Joshi
7c4a934eae
Merge pull request #1066 from getagentseal/fix/cli-vitest-ci-timeouts
test(cli): restore CI timeout headroom + lock-suite quarantine lost in packages/cli split
2026-08-21 08:32:50 -07:00
iamtoruk
a23d0526a0 test(cli): restore CI timeout headroom and lock-suite quarantine lost in packages/cli split
The packages/cli move (dc97ab49, Jul 26) forked vitest.config.ts and the test
script off main before three since-merged CI-stability fixes existed there,
and #921's later "port upstream fixes" pass (f3f5814b) missed all three since
it only cross-referenced one specific main commit:

- 30037b33: global vitest testTimeout raised from the 5s default to 30s
  (real-I/O tests exceed 5s under CI runner load)
- 8c758ddf: file-level 30s timeout on cli-status-menubar.test.ts (spawns the
  real CLI; individual cases observed needing 6-8s on a shared 2-core runner)
- 4ca2d482: cache-refresh-lock tests excluded from the parallel `test` run and
  quarantined behind a serial `test:locks` script (they contend for the fork
  pool with the spawned-subprocess tests and starve everyone under load)

None of this showed up until #923 wired `npm test --workspace=codeburn` into
CI for the first time, which then failed deterministically at the vitest
default 5000ms on exactly the three test categories these fixes cover.

Ports all three, matching main's current config/script.
2026-08-21 08:28:55 -07:00
Resham Joshi
9ba327c3e2
Merge branch 'feat/core-extraction' into fix/ci-run-cli-suite 2026-08-21 08:14:28 -07:00
Resham Joshi
35209cadad
Merge pull request #1065 from getagentseal/fix/branch-cli-suite-red
fix(cli): hermetic DeepSeek pricing + platform-aware proxy-path test, unblocks #923 cli CI job
2026-08-21 08:14:24 -07:00
Resham Joshi
1412555f3c
Merge pull request #922 from ozymandiashh/fix/semgrep-guard-fires
ci: make the bracket-assign guard actually match its targets
2026-08-21 08:13:56 -07:00
iamtoruk
5ea455ecbe test(cli): price off the bundled snapshot; fix platform-dependent proxy-path assertion
Two pre-existing CLI-suite failures blocked #923's new cli CI job on Linux:

- tests/models.test.ts DeepSeek v4 pricing (x3): loadPricing() fetched the
  live LiteLLM table during tests, and live data wins over the bundled
  snapshot, so the DeepSeek v4 assertions went red when upstream repriced.
  Ports main's fix (4866332b): CODEBURN_PRICING_SNAPSHOT_ONLY skips the fetch
  and prices purely off the bundled snapshot; env-isolation.ts sets it for
  the whole suite.

- tests/parser-proxy-pricing.test.ts case-insensitive isProxiedPath: the
  branch's isProxiedPath/normalizeProxyPath is byte-identical to main's
  (case folding is deliberately darwin/win32-only, per the function's own
  comment). The test hardcoded the macOS-only expectation, so it fails by
  design on Linux CI. Main hit and fixed the same thing (bcf11552); this
  ports that fix verbatim: assert the platform-correct behavior instead of
  a hardcoded `true`.
2026-08-21 08:11:34 -07:00
Resham Joshi
f4d44f5a61
Merge branch 'feat/core-extraction' into fix/ci-run-cli-suite 2026-08-21 08:03:52 -07:00
Resham Joshi
8cd7c2cf69
Merge pull request #921 from ozymandiashh/fix/port-test-fixes
test: port upstream fixture and retry fixes (2a4b8f2)
2026-08-21 08:03:34 -07:00
ozymandiashh
f29d27e39d feat(sync): push git attribution spans, with the hardening that followed
Ports three upstream commits this branch never received: the attribution
feature (1bf7206), the review hardening on top of it (ccee28a), and the
security follow-up that closed credential-leak paths and added session
retraction (50c8251).

They are ported as an end state rather than in sequence. Two and three revise
one, so replaying them in order would have introduced the very issues they fix
and then removed them again — and anything missed in the third pass would have
shipped a feature with a reopened hole, which is the specific way this port
could have gone wrong. The credential-leak paths that commit closes are
enumerated and checked off individually against the result.

One correction to an earlier draft of this message, which claimed no new
unkeyed digest is introduced. That was wrong: stateHash in sync/otlp.ts is a
new unkeyed sha256, and it feeds deriveSpanId, so it is an input to a value
that goes on the wire. It is not a D1 violation — D1 governs core's fingerprint
module and its caller-supplied key, while stateHash is a local ledger
discriminator computed over data that is itself sent in cleartext, so it hides
nothing and leaks nothing. But the sentence was false and is worth correcting
rather than quietly dropping.

This branch now carries #931's commit (c467548, "fix(sync): key the device,
span and trace digests") beneath this one — cherry-picked onto the shared base
so the history stays two clean commits. That ordering is load-bearing:
reconciliation is mandatory in every merge order, not optional. git merge-tree
reports no conflict against #931 in either direction, yet the merged file does
not compile: #931 drops the createHash import and gives the derive functions a
privacyKey first argument, so an unreconciled attribution section leaves
stateHash with an undefined symbol and two one-argument call sites. Rebasing
replays the same breakage, which is why the earlier "land #931 first, OR
reconcile" framing was wrong.

The two call sites are reconciled INTO #931's keyed signatures, in the
direction #931 demands: buildAttributionOtlpPayload obtains the persisted host
privacy key exactly as buildOtlpPayload does — one getPersistedHostPrivacyKey
call per builder, no second source of the key — and threads it into
deriveTraceId and deriveSpanId. This is the security point of the
reconciliation: loosening the signatures back to one argument would
reintroduce exactly the unkeyed span and trace ids #931 exists to remove, in
new code.

stateHash stays unkeyed, deliberately: it is a local ledger discriminator over
One more merge-compat fix, in #931's own test file (sync-privacy-key.test.ts):
the concurrency fixture path was built from process.cwd(), which is the repo
root under `--root packages/cli` — the worker then exited on a nonexistent
file before writing its ready file and the race test timed out. The path is
now anchored to the test file's own location (fileURLToPath(import.meta.url)).
This is the only line of #931's tree this branch touches; 37a5b46 remains a
verbatim copy of c467548.

Second fix in #931's tree, same motivation: the concurrency race test
adopted with only a 50ms budget. createKeyFileExclusive polled the winner's
file 5x10ms after EEXIST, and the strict entry check refused an 'invalid'
file INSTANTLY — but the winner's create (open) and write are separate
syscalls, and under load the loser can read the still-empty file either at
entry or inside the poll. Both windows now share one bounded awaitValidKey
(500ms) that ADOPTS the winner's key when it lands and otherwise throws the
same refusal. Nothing is ever overwritten; a file left invalid by a crash or
truncated write still fails loudly. This is the second #931 file this branch
touches; 37a5b46 remains a verbatim copy of c467548.
2026-08-05 18:50:23 +03:00