Commit graph

1293 commits

Author SHA1 Message Date
iamtoruk
8a812abfe5 fix(codex): stop double-billing reasoning, price cache writes at the explicit rate only
Port of #1078 (plus its #1084 hardening) onto feat/core-extraction, whose
pricing seam moved out of the provider decoder.

Fix A: OpenAI bills reasoning tokens as part of output_tokens, not on top of
it. `billableOutputTokens` in models.ts is the single source of truth for the
claude+codex carve-out; the host pricing pass and cachedCallToApiCall (the
cold/warm twins) and the models/audit display sums all route through it.
codexCredits loses its `reasoningTokens` param, which added the same
double-count back on.

Fix B: the core codex decoder reads `cache_write_input_tokens` and clamps it
into the uncached input, but leaves the bucket split to the host: only a model
whose pricing source publishes a real cache-write rate
(`cacheWriteCostIsExplicit`) may move tokens into the cache-write bucket, since
buildCosts fabricates 1.25x input when a source omits one and OpenAI charges
nothing extra before gpt-5.6. The pricing cache file is versioned so a file
written by a pre-fix binary can't read that flag back as undefined.

Cache ladder: codex results cache 12 -> 13, codex parse version gains
`-codex-pricing-v1` (the bucket move does not self-heal on read), daily cache
27 -> 28.
2026-08-21 14:44:46 -07:00
Resham Joshi
fe65abe346
Merge pull request #1087 from getagentseal/fix/rehome-adversarial
fix: adversarial-validation findings from #1086 (web-search faithfulness, taskOpen gate, custom-tool timing)
2026-08-21 14:10:45 -07:00
iamtoruk
81a6718add fix(core): cover the codex timing gate and the custom-tool transport
Two gaps the adversarial pass found in the throughput rehome.

The `taskOpen` gate had no behavioral coverage: deleting it left all 613 core
tests green, because the existing cases pin the flag's VALUE and never its
consequence — their resumed pass holds no pending calls, so an earlier
condition already blocks attribution. The new case splits a task with two
token_counts, so the resumed pass holds a real call and real generated tokens
but never saw the task_started. Without the gate its task_complete takes the
whole 6s window for the one call it happens to hold — double that call's share,
with the first call left unattributed. With it, nothing is attributed.

The timing branches spoke only function_call / function_call_output, while main
and the ported codex-throughput.ts also count Codex Desktop's custom-tool
transport. On the real corpus that is 11,390 tool pairs across 134 rollouts,
whose wait time was being counted as active: GPT-5.5 drops from 506,373 to
490,337 active seconds (36.5 -> 37.7 Tok/s), gpt-5.6-sol from 66,194 to 54,120
(35.3 -> 43.2). Tool NAMING for custom_tool_call stays unported, so the start
is captured in its own timing-only branch. Cost, calls and tokens are
unchanged: codex totals are still byte-identical to the base branch over the
1326-session corpus.

Also lands the adversarial append-split fixture as a permanent regression test:
cold parse == warm append parse at EVERY line split point of a rollout with two
complete tasks and a third left open at EOF, plus a three-pass variant and an
idempotence check. It kills both ways of getting the resume point wrong —
replaying past the boundary (callCount not truncated) and resuming at
end-of-file (the open task's window lost). The validator's full O(n^2) three-
pass sweep is trimmed to the boundary-crossing pairs; it adds ~90 parses and no
extra mutation coverage.
2026-08-21 14:04:32 -07:00
iamtoruk
ab3294a3e2 fix(cli): report cline-cli web-search requests, bill zero of them
Review correction. Upstream zeroes only the PRICING argument — it passes a
literal 0 as calculateCost's sixth parameter (main src/providers/cline-cli.ts
:299) while ALWAYS emitting the real count on a per-message call; only the
session-rollup path emits 0 (:349), because that path has no tool blocks to
count. The rehome zeroed the EMITTED metric on every estimated call instead,
which prices correctly but destroys the analytics field.

The count now always rides on the call, and the suppression moves to where the
billing actually happens: the pricing pass passes 0 web-search requests for
cline-cli, whose `fetch_web_content` is a page fetch rather than a billable
provider-side search. The pass is the single place estimated calls are priced
(parser.ts prices every provider call there before anything is cached), and
cline-cli's costUSD is persisted, so the cache read path cannot re-bill it
either.

The test asserted toBe(0) where main yields 1 — it certified the regression.
It now proves both halves at once: the count is emitted on the estimated AND
the metered call, and the estimated cost equals that of an identical session
with no fetch. Reverting either half fails it (count -> 0, or cost +$0.01).
2026-08-21 14:04:32 -07:00
ozymandiashh
8904b11b98
Merge pull request #1086 from getagentseal/feat/rehome-landing
feat(cli): rehome cline-cli and codex throughput (landing #940)
2026-08-22 00:01:28 +03:00
iamtoruk
8ec2753cbd feat(cli): rehome codex tool-excluded active throughput
The second half of #940 (@ozymandiashh): upstream `main` measures Codex
throughput — a task's wall time minus its recorded tool wait, divided across
the task's calls by generated tokens — and none of it exists on this branch.
Added on one side only, so a `main` merge would land `src/codex-throughput.ts`
at a path npm workspaces does not build.

Rehomed against main as it ships today, with one deliberate divergence from
#940, which the maintainer decided: main's resume design wins.

  - Timing state is captured ONLY at a `task_started` boundary, where every
    per-task accumulator is provably empty, and a task's calls are buffered
    until its window is known. No recorded call is ever mutated after it has
    been handed to the host. #940's alternative — threading the open task
    window through the serialized state and back-patching earlier-pass calls
    via applyCodexTimingPatches — is dropped in full.

  - The branch's Phase-4 token-decode resume is untouched: it stays any-offset
    and round-trip proven. Marrying the two needed one adaptation, since core
    decodes records and never sees bytes: the decoder now reports its last
    task_started as a `checkpoint` (record index + call count + state), and the
    CLI turns that index into a byte offset and replays only the calls before
    it, letting the still-open task re-derive. A pass that crosses no boundary
    keeps the previous one; a cold decode of a file with no task_started at all
    falls back to end-of-file with `taskOpen: false`, so a task_complete whose
    window this pass never saw attributes nothing rather than spreading a whole
    task's active time over part of its tokens.

Restores the three fad84662 review fixes that #940 reverted: the discovery
fast path already short-circuits on cachedProject before isValidCodexSession
(unchanged here, verified); payload-level `duration_ms` outranks any nested one
(`timingDuration ?? timingNumber('duration_ms')`), so a duration buried in an
oversized mcp_tool_call_end's invocation.arguments can no longer inflate tool
wait; and MIN_WIDE stays 90 with the Tok/s column behind a showTps gate rather
than jumping to 130 and costing 90-129 column terminals their two-column
dashboard. Also ports the fork-suppressed-task_started regression test and the
depth-1 payloadString helper (main 1d36f444/497f6556), which the branch lacked.

Scope discipline: main's codex pricing work (billableOutputTokens, #1078) is
NOT dragged along — that is #1083 — and neither are its unported parser
changes (custom-tool transport, exact token counts and MCP names on oversized
lines), so cost, calls and tokens are untouched. Verified on a 1326-session
real corpus: codex totals byte-identical to the base branch, with 1302 of 1328
model slices now carrying timing (36.5 Tok/s on GPT-5.5).

CODEX_CACHE_VERSION takes 12, clear of main's ladder (11 as of #1078) so a
cache written by either line can never be read as current by the other, and
the codex parse version bumps in lockstep so session-cache.json cannot keep
serving timing-less turns without invoking the parser.
2026-08-21 13:37:22 -07:00
iamtoruk
d64b29f64d feat(cli): rehome the cline-cli provider into the workspace layout
Upstream `main` ships a provider this branch has no counterpart for: the
Cline CLI (npm `cline`, 3.x), whose sessions live in a layout unrelated to the
VS Code extension tree `cline.ts` reads. Files added on one side only produce
no merge conflict, so a `main` merge would happily create `src/providers/
cline-cli.ts` — a path npm workspaces does not build and no registry imports.
Green build, and a whole provider quietly does not exist.

Ported from #940 (@ozymandiashh), which carried it faithfully: identical dedup
keys, all 34 upstream test cases, and the clean phase-8 split — discovery and
file I/O host-side, pure record decode in @codeburn/core, registered next to
its neighbours and deliberately separate from the shared vscode-cline tier.

Two changes on top of that port:

  - `observations.ts` follows the post-#1074 conventions: the model is routed
    through `normalizeModelIdentifier` at the observation boundary like every
    other provider, and measured cost is carried the way the sibling decoders
    carry it rather than through a cast.

  - the estimated-cost path reports zero web-search requests. Upstream prices
    that path with a hardcoded 0; letting the decoded `fetch_web_content`
    count reach the pricing pass would bill $0.01 per fetch on top of tokens,
    a billing change nobody asked for. A metered call keeps the real count —
    its dollar figure comes from the CLI, so nothing prices off it.

DAILY_CACHE_VERSION takes 27 (MIN_SUPPORTED 27): every historical Cline CLI
session contributes usage no older rollup ever contained, and usage-aggregator
serves every day before today from that cache for ten years, so without the
bump an upgrading user would keep cline-cli-less history forever while today's
numbers silently included it.
2026-08-21 13:27:57 -07:00
ozymandiashh
602ed3446d
Merge pull request #1080 from ozymandiashh/fix/copilot-durable-fingerprint-migration
fix(core): preserve cache and schema compatibility after #1074
2026-08-21 23:27:05 +03:00
ozymandiashh
2223f8523e fix(core): reconcile #1074 migrations without data loss
Preserve and re-key durable Copilot history across fingerprint and privacy-key changes, retain local sent-ledger aliases for all affected providers, and retry transient durable reads without clearing cached turns.

Restore the published observation 0.2.0 contract byte-for-byte and move the model identifier hardening to observation 0.3.0.
2026-08-21 22:25:53 +03:00
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