diff --git a/.agents/pm/history/pm-http-33ck.jsonl b/.agents/pm/history/pm-http-33ck.jsonl index 13a7899..5c4807a 100644 --- a/.agents/pm/history/pm-http-33ck.jsonl +++ b/.agents/pm/history/pm-http-33ck.jsonl @@ -1 +1,15 @@ {"hash_algorithm":"sha256","ts":"2026-11-02T09:17:27.180Z","author":"pi-glm","author_source":"asserted","agent_harness":"pi","agent_model":"glm-5.3:cloud","agent_model_source":"environment","agent_instance":"7687d94a08e852f40579b9f7","agent_provenance":{"model":{"value":"glm-5.3:cloud","source":"environment"},"effort":null,"role":{"value":"implementer","source":"argv"},"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-http-33ck"},{"op":"add","path":"/metadata/title","value":"Distinguish exact serialized byte costs from heuristic token estimates in difference packets"},{"op":"add","path":"/metadata/description","value":"Tracks gh:unbraind/pm-http#5 (filed 2026-10-29, missed by the blind issue watch). src/core.ts computes tokens(value) as ceil(max(JSON, TOON UTF-8 bytes)/4) and reports it as estimatedTokens with its budget; the epic's bounded-difference-packet contract is met, but agent consumers cannot distinguish an enforceable serialized-byte ceiling from an approximate model-token cost (ceil(bytes/4) is neither a tokenizer count nor a guaranteed upper bound). Request: expose the exact packet byte costs alongside the estimate, and name the estimator explicitly in receipts/docs so a context budget claim cannot be read as a measured token count. Static proposal; no transport defect reproduced."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":2},{"op":"add","path":"/metadata/tags","value":["api","gh:unbraind/pm-http#5","measurement"]},{"op":"add","path":"/metadata/created_at","value":"2026-11-02T09:17:27.180Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-11-02T09:17:27.180Z"},{"op":"add","path":"/metadata/author","value":"pi-glm"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"543e8cd39f9afc2ccbeccb817ac4bd73838bf3c3962e0c887fc208f2f6ed1db9","item_hash_version":3,"message":"","event_class":"substantive","record_hash_version":1,"record_hash":"08179c93430b1ddc200da9c177465a79ca22793a63f1b13fb3ed8f68d99df414"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:11:33.640Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:11:33.640Z"},{"op":"add","path":"/metadata/assignee","value":"codex-sol"},{"op":"add","path":"/metadata/claim_principal","value":"codex-sol"}],"before_hash":"543e8cd39f9afc2ccbeccb817ac4bd73838bf3c3962e0c887fc208f2f6ed1db9","after_hash":"adff15f46680dea0dff98656cb2307446e5511a520e2d969d3c8b3cdead050ec","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"79e4284917ca3639387d05fcce6801dbc1e887cb3d16d43b99239e7dda163e47"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:11:35.324Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:11:35.324Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"adff15f46680dea0dff98656cb2307446e5511a520e2d969d3c8b3cdead050ec","after_hash":"4d8e184976b6606b37655b1e686f7bbde394d715c3edcc4c0cf39969ce96efb4","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"3b1a66b13fb4fc0ffee8f3a78b3658b2fe14cc8be28f88595e55cca01a46233f"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:11:37.108Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:11:37.108Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-11-06T14:11:37.107Z","author":"codex-sol","text":"Confirmed current main retains the local byte/4 estimator and independent packet fitting loop. Issue 5 remains applicable; companion G6 requests the SDK fitter with the existing skip policy. Plan: failing real no-git tracker tests, exact self-inclusive JSON/TOON byte accounting including newline, explicit heuristic budget naming, SDK fitting, boundary/revert proofs, documentation and full review loop. Installed SDK already exports the fitter, so no pin change is required."}]}],"before_hash":"4d8e184976b6606b37655b1e686f7bbde394d715c3edcc4c0cf39969ce96efb4","after_hash":"b83540c5d1f5e3a41be8b6177e431e27a64f969c06936baff90b20f956785a0c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"6f1c5da2237f856bb17d7564b367c3567938c8034a20d20a67de4a13435f3ac1"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:16:32.947Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-11-06T14:16:32.947Z","author":"codex-sol","text":"Failing-first proof on main: both initial real no-git tracker tests failed because exact byte fields were absent (measured JSON 2301 / TOON 1775 bytes for the Unicode/digest fixture). Fixed packets use the installed SDK fitter, skip admission, canonical estimation, self-inclusive renderer bytes, and typed safe fitting refusals. Targeted packet tests pass, including minimum receipts and decimal/budget boundaries. Typecheck, lint, docstring and unchanged duplication gates pass. Historical 817 and raw-body figures are explicitly scoped as heuristics; no current hosted or public data was accessed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:16:32.947Z"}],"before_hash":"b83540c5d1f5e3a41be8b6177e431e27a64f969c06936baff90b20f956785a0c","after_hash":"689323b1690792bb11dbbef3d5e9fecb689f8d5dd8d4fefe2b326a2633d91fd9","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"16b4f9b93545dcd302faee8508299a27f6cedb7e0accf6b76498008f68990405"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:16:42.480Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:16:42.480Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node --test --test-concurrency=2 test/packet.test.ts","scope":"project","provenance":{"author":"codex-sol","created_at":"2026-11-06T14:16:42.421Z","source_kind":"local_mutation","source_ref":"exact-packet-byte-costs"}}]}],"before_hash":"689323b1690792bb11dbbef3d5e9fecb689f8d5dd8d4fefe2b326a2633d91fd9","after_hash":"39fda82efdf44d310a3ea572d418e3faa3a268044d6fed345bb774a3497439ae","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"1e7bfd876f6cc8397e4e620f13dc62690c6172c741861319c57ba51778066d27"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:17:31.265Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:17:31.265Z"},{"op":"add","path":"/metadata/files","value":[{"path":"index.ts","scope":"project"},{"path":"src/commands.ts","scope":"project"},{"path":"src/core.ts","scope":"project"},{"path":"src/derive.ts","scope":"project"},{"path":"test/commands.test.ts","scope":"project"},{"path":"test/packet.test.ts","scope":"project"},{"path":"test/real-data.acceptance.ts","scope":"project"},{"path":"test/revert.acceptance.ts","scope":"project"}]}],"before_hash":"39fda82efdf44d310a3ea572d418e3faa3a268044d6fed345bb774a3497439ae","after_hash":"fe163930759f748658213592c2d1599d351eddb7ac84b62d2b5b78913a631ef1","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"c99dc68976470184b27a1f4b90bd6fd58ab94d95950e7a42c1bbc23d7b0a28ba"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:17:35.622Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:17:35.622Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"ARCHITECTURE.md","scope":"project"},{"path":"docs/real-data.md","scope":"project"},{"path":"README.md","scope":"project"}]}],"before_hash":"fe163930759f748658213592c2d1599d351eddb7ac84b62d2b5b78913a631ef1","after_hash":"eba4f907f2476d95811b360ad8fd8d558b4b3809b67dd52ca719be05a2eca9ee","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"61368882abb7c50f650ded0e59ebe5dc2d7cf00dd906cf002b4816252c87ba0c"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:20:25.355Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/1","value":{"command":"node test/revert.acceptance.ts issue5-packet issue5-packet-host issue5-skip issue5-byte-framing issue5-sdk-refusals","scope":"project","provenance":{"author":"codex-sol","created_at":"2026-11-06T14:20:25.211Z","source_kind":"local_mutation","source_ref":"exact-packet-byte-costs"}}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:20:25.355Z"}],"before_hash":"eba4f907f2476d95811b360ad8fd8d558b4b3809b67dd52ca719be05a2eca9ee","after_hash":"b666be11b3d568e261bc0e41bd602f404b8daee61d239bf97bdb059ba397e25c","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"008794a0616eed963d1f7fddb68db6f0674b24b2dae74f9e77938a5312c9008f"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:20:29.145Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-11-06T14:20:29.145Z","author":"codex-sol","text":"Validation: packet linked test 4/4 passed; focused HTTP/command tests 29/29 passed. Behavioral revert proofs 5/5 passed in disposable copies: original main differencePacket fails all three packet regressions and native-host byte assertions; restoring skip admission, JSON newline accounting or SDK refusal mapping makes its corresponding regression pass after the intentional revert fails. npm typecheck, lint, docstring (96 declarations) and duplication (0 percent) passed at unchanged gates. The full npm gate is also running with isolated HOME and Git identity; CI will run full npm/Bun gates on the PR head. No tokenizer or end-to-end task-cost claim is made."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:20:29.145Z"}],"before_hash":"b666be11b3d568e261bc0e41bd602f404b8daee61d239bf97bdb059ba397e25c","after_hash":"a608985bc93837ddf637446976e9c53d29538e5c9b0641e9ba75e8314655d123","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"bb6343c50ac972d0ea25dd7369de1717d7483c48a9ff86bb0a0b3e5cf14e6ba6"} +{"hash_algorithm":"sha256","ts":"2026-11-06T14:20:32.170Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T14:20:32.170Z"}],"before_hash":"a608985bc93837ddf637446976e9c53d29538e5c9b0641e9ba75e8314655d123","after_hash":"672d756ea326192362cfeef06ee8721acf298f418b7c977d766172d8fc56c2c0","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"f8d924686daeee01f31261b05ac0cedbdb6d1f2856e7bb77ae4516017c7707b1"} +{"hash_algorithm":"sha256","ts":"2026-11-06T15:42:38.085Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T15:42:38.085Z"},{"op":"add","path":"/metadata/assignee","value":"codex-sol"},{"op":"add","path":"/metadata/claim_principal","value":"codex-sol"}],"before_hash":"672d756ea326192362cfeef06ee8721acf298f418b7c977d766172d8fc56c2c0","after_hash":"a96be3d0d35a4b14590daa075435a33f9e5a09e238789b32721d1279367f028e","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"babd641afb0c383b6da6db43f4ff7b51cf9cfd9d22a392646a432731b9b67003"} +{"hash_algorithm":"sha256","ts":"2026-11-06T15:42:42.063Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-11-06T15:42:42.063Z","author":"codex-sol","text":"Full isolated npm and Bun release gates passed: each ran 143/143 tests, 100 percent statements/branches/functions/lines across 20 configured source files and 2/2 packed npm/Bun investigations against the declared minimum SDK. Node 22/26 CI and CodeQL also passed on 12b71dc. The required explicit review remains queued with no findings received. Follow-up correction: pin the revert harness to historical main e2074b1, so its original-function proofs remain reproducible after the fix is merged; production implementation is unchanged. Re-running all five disposable proofs and targeted static gates before the correction push. PR: https://github.com/unbraind/pm-http/pull/21."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T15:42:42.063Z"}],"before_hash":"a96be3d0d35a4b14590daa075435a33f9e5a09e238789b32721d1279367f028e","after_hash":"ab9c44e2134bf67c98eacb6d3c3b23bd76833140ac6dd4c919bbfeb2b3fc07ad","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"ae839da23334d3f74ad3b76e76130f90cd0e6138deaba957bbab984e338e47e4"} +{"hash_algorithm":"sha256","ts":"2026-11-06T15:46:37.786Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-11-06T15:46:37.785Z","author":"codex-sol","text":"Pinned-baseline correction verified: all five disposable revert proofs passed again; typecheck and lint passed. A real disposable git clone advanced origin/main to the fixed function: the pinned e2074b1 harness passed, while reverting the harness to moving origin/main failed its identity check. This proves the harness remains usable after main advances. Refreshed current main and independently verified its tracker history remains a byte prefix of this branch. Production source is byte-identical to the implementation that passed both full local release gates and prior-head CI. The correction push will receive fresh exact-head CI and the queued review."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T15:46:37.786Z"}],"before_hash":"ab9c44e2134bf67c98eacb6d3c3b23bd76833140ac6dd4c919bbfeb2b3fc07ad","after_hash":"085cd34ad329b7a6dad213f45577a693e652563dc2be5b8dee00540f0501c3b6","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"substantive","record_hash_version":1,"record_hash":"f8bd3930523c1a94f6ff8db9b52422cab1d2fc887424a5c3474bcf286d2902ad"} +{"hash_algorithm":"sha256","ts":"2026-11-06T15:46:40.919Z","author":"codex-sol","author_source":"asserted","agent_harness":"codex","agent_model":"gpt-6.1-sol","agent_model_source":"probe","agent_instance":"f517fbd172a3ccf8f6983ead","agent_provenance":{"model":{"value":"gpt-6.1-sol","source":"probe"},"effort":{"value":"high","source":"probe"},"role":{"value":"implementer","source":"argv"},"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-11-06T15:46:40.919Z"}],"before_hash":"085cd34ad329b7a6dad213f45577a693e652563dc2be5b8dee00540f0501c3b6","after_hash":"c555c7534a8275a898fd6c1b708508ea7ad3da58ab34b883d0a38019f6cc14a2","item_hash_version":3,"context":{"agent_provenance_outcomes":{"model":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"effort":{"status":"resolved","resolver":"codex_session_file","rule_version":"v1"},"version":{"status":"unavailable","reason":"harness_unavailable","resolver":"ai_agent_version","rule_version":"v1"}}},"event_class":"maintenance","record_hash_version":1,"record_hash":"85e09b6e0e1aa2b48afe92ff49082084d1b261e86bc66c86b1fbf0a1d03d13a5"} diff --git a/.agents/pm/issues/pm-http-33ck.toon b/.agents/pm/issues/pm-http-33ck.toon index 640e68c..412a643 100644 --- a/.agents/pm/issues/pm-http-33ck.toon +++ b/.agents/pm/issues/pm-http-33ck.toon @@ -2,10 +2,32 @@ id: pm-http-33ck title: Distinguish exact serialized byte costs from heuristic token estimates in difference packets description: "Tracks gh:unbraind/pm-http#5 (filed 2026-10-29, missed by the blind issue watch). src/core.ts computes tokens(value) as ceil(max(JSON, TOON UTF-8 bytes)/4) and reports it as estimatedTokens with its budget; the epic's bounded-difference-packet contract is met, but agent consumers cannot distinguish an enforceable serialized-byte ceiling from an approximate model-token cost (ceil(bytes/4) is neither a tokenizer count nor a guaranteed upper bound). Request: expose the exact packet byte costs alongside the estimate, and name the estimator explicitly in receipts/docs so a context budget claim cannot be read as a measured token count. Static proposal; no transport defect reproduced." type: Issue -status: open +status: in_progress priority: 2 tags[3]: api,"gh:unbraind/pm-http#5",measurement created_at: "2026-11-02T09:17:27.180Z" -updated_at: "2026-11-02T09:17:27.180Z" +updated_at: "2026-11-06T15:46:40.919Z" author: pi-glm +comments[5]{created_at,author,text}: + "2026-11-06T14:11:37.107Z",codex-sol,"Confirmed current main retains the local byte/4 estimator and independent packet fitting loop. Issue 5 remains applicable; companion G6 requests the SDK fitter with the existing skip policy. Plan: failing real no-git tracker tests, exact self-inclusive JSON/TOON byte accounting including newline, explicit heuristic budget naming, SDK fitting, boundary/revert proofs, documentation and full review loop. Installed SDK already exports the fitter, so no pin change is required." + "2026-11-06T14:16:32.947Z",codex-sol,"Failing-first proof on main: both initial real no-git tracker tests failed because exact byte fields were absent (measured JSON 2301 / TOON 1775 bytes for the Unicode/digest fixture). Fixed packets use the installed SDK fitter, skip admission, canonical estimation, self-inclusive renderer bytes, and typed safe fitting refusals. Targeted packet tests pass, including minimum receipts and decimal/budget boundaries. Typecheck, lint, docstring and unchanged duplication gates pass. Historical 817 and raw-body figures are explicitly scoped as heuristics; no current hosted or public data was accessed." + "2026-11-06T14:20:29.145Z",codex-sol,"Validation: packet linked test 4/4 passed; focused HTTP/command tests 29/29 passed. Behavioral revert proofs 5/5 passed in disposable copies: original main differencePacket fails all three packet regressions and native-host byte assertions; restoring skip admission, JSON newline accounting or SDK refusal mapping makes its corresponding regression pass after the intentional revert fails. npm typecheck, lint, docstring (96 declarations) and duplication (0 percent) passed at unchanged gates. The full npm gate is also running with isolated HOME and Git identity; CI will run full npm/Bun gates on the PR head. No tokenizer or end-to-end task-cost claim is made." + "2026-11-06T15:42:42.063Z",codex-sol,"Full isolated npm and Bun release gates passed: each ran 143/143 tests, 100 percent statements/branches/functions/lines across 20 configured source files and 2/2 packed npm/Bun investigations against the declared minimum SDK. Node 22/26 CI and CodeQL also passed on 12b71dc. The required explicit review remains queued with no findings received. Follow-up correction: pin the revert harness to historical main e2074b1, so its original-function proofs remain reproducible after the fix is merged; production implementation is unchanged. Re-running all five disposable proofs and targeted static gates before the correction push. PR: https://github.com/unbraind/pm-http/pull/21." + "2026-11-06T15:46:37.785Z",codex-sol,"Pinned-baseline correction verified: all five disposable revert proofs passed again; typecheck and lint passed. A real disposable git clone advanced origin/main to the fixed function: the pinned e2074b1 harness passed, while reverting the harness to moving origin/main failed its identity check. This proves the harness remains usable after main advances. Refreshed current main and independently verified its tracker history remains a byte prefix of this branch. Production source is byte-identical to the implementation that passed both full local release gates and prior-head CI. The correction push will receive fresh exact-head CI and the queued review." +files[8]{path,scope}: + index.ts,project + src/commands.ts,project + src/core.ts,project + src/derive.ts,project + test/commands.test.ts,project + test/packet.test.ts,project + test/real-data.acceptance.ts,project + test/revert.acceptance.ts,project +tests[2]{command,scope,provenance{author,created_at,source_kind,source_ref}}: + node --test --test-concurrency=2 test/packet.test.ts,project,codex-sol,"2026-11-06T14:16:42.421Z",local_mutation,exact-packet-byte-costs + node test/revert.acceptance.ts issue5-packet issue5-packet-host issue5-skip issue5-byte-framing issue5-sdk-refusals,project,codex-sol,"2026-11-06T14:20:25.211Z",local_mutation,exact-packet-byte-costs +docs[3]{path,scope}: + ARCHITECTURE.md,project + docs/real-data.md,project + README.md,project body: "" diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6c72ea3..cabf795 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -50,7 +50,7 @@ Contracts define status alternatives, required selected headers (redacted values For each probe, observations sort by timestamp and stable ID. Find the first contract violation and its preceding last satisfying observation. If no violation exists, compare the latest pair; absent predecessors remain explicit null identities. Compare status, outcome, body digest/length, total timing, retry receipts, selected headers and each structural shape field. Include source observation IDs/timestamps, verdict reasons and exact per-field differences. Unknown evidence never becomes last-good evidence. Independent missing rows identify each absent observation, contract or complete capture. -The packet prioritizes missing-evidence rows, then deterministic per-probe difference rows. It estimates the larger actual JSON/TOON wire size, including newline and the self-inclusive estimate/receipt, as bytes divided by four rounded up. Rows that do not fit are omitted; receipts state exact omitted/returned counts and commands to restore the full diff or missing-evidence list. The estimate is conservative across these two serializations, not model-token exact. Source reads are complete even when packet rows are omitted. +The packet prioritizes missing-evidence rows, then deterministic per-probe difference rows. The SDK `fitOutputEnvelope` owns whole-envelope convergence, work ceilings and row admission with `policy: 'skip'`; the package supplies its inert JSON/TOON renderers including the final newline. `bytes.json` and `bytes.toon` report exact self-inclusive UTF-8 lengths; `output_fit.output_bytes` is their maximum. To populate both lengths without another fixed-point loop, envelope construction subtracts each format's length difference from the SDK maximum. The accounting fields are scalar integers spelled identically in both formats, so that difference is invariant under their digit widths. The SDK converges and verifies the final maximum including all domain and SDK receipts. Its canonical default estimator gives `estimatedTokens = ceil(max(bytes.json,bytes.toon)/4)`, retained as a compatibility field and explicitly labeled heuristic units. This is no model-token measurement or upper-bound guarantee. Rows that do not fit are omitted; receipts state exact omitted/returned counts and commands to restore the full diff or missing-evidence list. Source reads remain complete. Typed infeasible, unstable and work-limit refusals produce no partially certified packet. ## Limits and backend boundary diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fa9a96..1c8799b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - Produce bounded difference packets and missing evidence ([pm-http-s2xn](https://github.com/unbraind/pm-http/blob/main/.agents/pm/features/pm-http-s2xn.toon)) - Recover concurrent journaled sends ([pm-http-95t1](https://github.com/unbraind/pm-http/blob/main/.agents/pm/features/pm-http-95t1.toon)) +### Fixed + +- Distinguish exact serialized byte costs from heuristic token estimates in difference packets ([pm-http-33ck](https://github.com/unbraind/pm-http/blob/main/.agents/pm/issues/pm-http-33ck.toon)) + ### Other - Build task-scoped HTTP investigation context ([pm-http-76jn](https://github.com/unbraind/pm-http/blob/main/.agents/pm/epics/pm-http-76jn.toon)) diff --git a/README.md b/README.md index d30840e..0aed669 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ const packet = await differencePacket(itemId, { ...options, budget: 1500 }); const missing = await missingEvidence(itemId, options); ``` -`observations(item, options)` returns complete stored observations and a completeness receipt. `compare(item, {...options, probe?})` returns historical verdicts and reasons. `differencePacket(item, {...options, budget?})` returns a self-accounted bounded packet, including omitted-row counts and commands to restore full context. `missingEvidence(item, options)` names each missing observation, contract or complete response. `listProbes`, `preview`, `reviewProbe` and the input/record interfaces are also exported. `Options` supports an injected native `PmClient`, an AbortSignal, expected revision, stable operation ID, protocol-boundary and SDK transition observers. +`observations(item, options)` returns complete stored observations and a completeness receipt. `compare(item, {...options, probe?})` returns historical verdicts and reasons. `differencePacket(item, {...options, budget?})` returns a self-accounted bounded packet with exact `bytes.json` and `bytes.toon` UTF-8 counts, including the final newline, all receipts, and the byte counts themselves. The SDK `output_fit` receipt records the maximum byte cost and retained source indices. Omitted-row counts and commands restore full context. `missingEvidence(item, options)` names each missing observation, contract or complete response. `listProbes`, `preview`, `reviewProbe` and the input/record interfaces are also exported. `Options` supports an injected native `PmClient`, an AbortSignal, expected revision, stable operation ID, protocol-boundary and SDK transition observers. ## Safety and limits @@ -118,7 +118,7 @@ const missing = await missingEvidence(itemId, options); - Capture defaults: 64 KiB body, 64 headers, 5 seconds total across DNS, connection, TLS, body and redirects. Hard ceilings: 1 MiB body/request body, 128 response headers, 16 KiB parser headers, 60 seconds total. Caps produce capture omissions or uncertainty. No body decompression. Cancellation through SIGINT/AbortSignal leaves no partial observation; a cancelled operation requires a new explicit identity. - Response scalar values, text and property names are never stored. JSON properties use SHA-256 identities; only structural types remain. Headers retain a narrow set of canonical nonopaque semantics. Set-Cookie, authorization-like, arbitrary/token-shaped values and known credential echoes are redacted. Other headers retain no opaque values. Digests cover captured bytes and explicitly identify prefix truncation. - Shape admission examines at most 64 properties/items per level, 256 structural nodes, and 12 levels. Omissions never count as a satisfied contract. Response controls and instruction delimiters render as inert escaped data. -- Each task holds at most 32 probes, 128 operations/observations and a 1 MiB state envelope. Move a continuing investigation to a new task when full. Packet budgets range from 256 to 1,000,000 estimated tokens; default 1,500. Estimation is the larger JSON/TOON UTF-8 size including receipt and newline, divided by four and rounded up; it is not a model tokenizer. +- Each task holds at most 32 probes, 128 operations/observations and a 1 MiB state envelope. Move a continuing investigation to a new task when full. Packet `--budget` / SDK `budget` ranges from 256 to 1,000,000 **heuristic units**; default 1,500. One unit is four serialized UTF-8 bytes: `estimatedTokens` (the compatibility field name) is `ceil(max(bytes.json, bytes.toon)/4)`. Thus both complete rendered packets, including newline and every accounting field, are bounded by `4 * budget` bytes. This is neither a tokenizer measurement nor a guaranteed upper bound on model tokens. No tokenizer or model encoding is used. `receipt.budgetUnit` names these heuristic units. The SDK fitter retains whole rows in order, skipping oversized rows and considering later rows. It refuses an infeasible mandatory receipt or exhausted fitting work with an actionable typed error. Boundary declarations are authorization by the operator, not network discovery or a monitoring schedule. Historical observations never assert current health. The tool does not retain raw bodies, excerpts, cookies or credential values, and does not load credential files. @@ -128,7 +128,7 @@ All probe, send, contract, compare, diff, missing and SDK operations work in an ## Evidence and development -See [measured ten-request investigation](docs/real-data.md) and [architecture](ARCHITECTURE.md). The measured packet contains 817 estimated tokens versus 25,847 for 103,388 captured raw body bytes; ten observations, 90 header redactions, ten HTTP calls, 65,151 ms including SDK setup/recording. No credentials were sent, and the temporary tracker was deleted. +See [measured ten-request investigation](docs/real-data.md) and [architecture](ARCHITECTURE.md). The historical packet measured 817 bytes/4 heuristic units versus 25,847 body-only heuristic units for 103,388 captured raw body bytes; these are not model-token counts or an end-to-end cost comparison. Exact renderer bytes were not retained for that historical run, and the new receipt changes packet sizes. The run recorded ten observations, 90 header redactions, ten HTTP calls, 65,151 ms including SDK setup/recording. No credentials were sent, and the temporary tracker was deleted. ```sh npm run release:check diff --git a/docs/real-data.md b/docs/real-data.md index 40e0de8..2516da0 100644 --- a/docs/real-data.md +++ b/docs/real-data.md @@ -7,10 +7,14 @@ Measured 2026-10-29T00:58:16.455Z. Ten explicit read-only requests in a temporar | Observations | 10 | | HTTP calls | 10 (2 public, 8 fixture) | | Captured raw response body bytes | 103388 | -| Raw body estimated tokens (bytes/4, rounded up) | 25847 | -| Difference packet estimated tokens | 817 | +| Raw body heuristic units (bytes/4, rounded up) | 25847 | +| Historical packet heuristic units (bytes/4, rounded up) | 817 | | Packet omitted rows | 0 | | Header redactions | 90 | | Total elapsed milliseconds, including SDK setup and recording | 65151 | Packet costs include the omission receipt and the larger serialized JSON/TOON format. Raw cost includes response bodies only, excluding headers and transcript/request overhead. Body values and keys are never retained. Historical captures are not current health. The incident changes from a successful numeric revision to a failing string revision; the packet identifies the first bad and preceding last good observations. Missing evidence: [{"probe":"pm-evidence","evidence":"complete-response"},{"probe":"pm-hypothesis","evidence":"complete-response"},{"probe":"pending","evidence":"observation"},{"probe":"pending","evidence":"contract"}]. Public schemas wider than 64 properties produce explicit structural omissions. + +The historical 817 figure is a byte-derived heuristic, not a tokenizer count or guaranteed model-token bound. Exact JSON and TOON lengths were not saved, so they cannot be reconstructed from that rounded value. The SDK fitter and new accounting fields change current packet costs; this table is an immutable historical measurement, not a current-size claim. + +Successful-task scope: this SDK harness performs tracker initialization and task creation, four probe declarations, three contract declarations, ten explicit `send` operations (two public and eight fixture observations), then `observations`, `differencePacket`, and `missingEvidence` reads. Native SDK recording/journal receipts, request/response metadata, setup returns, the observation-list and missing-evidence responses, and this report are excluded from the body-only baseline. The packet figure includes only its own historical receipt and framing. No automatic request retries are implemented, and this run recorded ten calls for ten sends; no recovery/retry transcript or all-command serialized costs were measured. Consequently these numbers do not establish an end-to-end token reduction. Public responses are not re-fetched for this correction. diff --git a/index.ts b/index.ts index c34d956..500394d 100644 --- a/index.ts +++ b/index.ts @@ -19,7 +19,7 @@ export const setContract = guarded(api.setContract); export const observations = guarded(derive.observations); /** Compare stored observations to current explicit contracts. */ export const compare = guarded(derive.compare); -/** Produce historical differences under a self-inclusive token budget. */ +/** Produce historical differences under a self-inclusive heuristic-unit budget with exact JSON/TOON UTF-8 byte costs. */ export const differencePacket = guarded(derive.differencePacket); /** Enumerate exact outstanding evidence requests. */ export const missingEvidence = guarded(derive.missingEvidence); diff --git a/src/commands.ts b/src/commands.ts index bd78fad..e2203f9 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -4,7 +4,7 @@ import { defineCommand } from '@unbrained/pm-cli/sdk/authoring'; import type { CommandHandlerContext, ExtensionApi, FlagDefinition } from '@unbrained/pm-cli/sdk/authoring'; import { addProbe, listProbes, preview, reviewProbe, send, setContract } from './api.ts'; import { compare, differencePacket, missingEvidence } from './derive.ts'; -import { HttpError, requireInput, wire } from './core.ts'; +import { HttpError, failure, requireInput, wire } from './core.ts'; import { FIELDS } from './store.ts'; /** Reject absent or array operands without echoing untrusted data. */ @@ -15,7 +15,7 @@ function texts(value: unknown): string[] { return value === undefined ? [] : (Ar function number(value: unknown): number | undefined { if (value === undefined) return undefined; requireInput(typeof value === 'string' && /^\d+$/.test(value), 'Numeric flag requires a nonnegative integer'); const parsed = Number(value); requireInput(Number.isSafeInteger(parsed), 'Numeric flag exceeds the safe integer range'); return parsed; } /** Convert one refusal into the native host's safe expected-error contract. */ function expected(error: unknown) { - const refusal = error instanceof HttpError ? error : new HttpError('sdk', 'HTTP context command failed', 'Check tracker health, schema file and access before retrying'); + const refusal = failure(error); return createPmCliExpectedError(`http ${refusal.code}: ${refusal.message}. Remedy: ${refusal.remedy}`, { exitCode: refusal.code === 'cancelled' ? 130 : refusal.code === 'conflict' ? 3 : 2, context: { code: refusal.code, ...refusal.details, required: refusal.remedy, recovery: { attempted_command: 'pm http [arguments omitted]', normalized_args: [], provided_fields: [] } } }); } /** Execute through the native host renderer and structured expected-error boundary. */ diff --git a/src/core.ts b/src/core.ts index b1daee0..256dbcc 100644 --- a/src/core.ts +++ b/src/core.ts @@ -1,6 +1,6 @@ import { createHash } from 'node:crypto'; import { encode } from '@toon-format/toon'; -import { PmCliError, EXIT_CODE } from '@unbrained/pm-cli/sdk'; +import { PmCliError, EXIT_CODE, OutputEnvelopeFitError, estimatePmOutputTokens } from '@unbrained/pm-cli/sdk'; /** Typed refusal with no raw network, environment or SDK diagnostic. */ export class HttpError extends Error { @@ -30,8 +30,8 @@ export function data(value: unknown): unknown { } /** Use host-selected TOON or JSON; untrusted text stays labeled data. */ export function wire(value: unknown, format: 'json' | 'toon'): string { const safe = data(value); return format === 'json' ? JSON.stringify(safe, null, 2) : encode(safe); } -/** Conservative byte/4 estimate covering both serialized host formats and newline. */ -export function tokens(value: unknown): number { return Math.ceil(Math.max(Buffer.byteLength(wire(value, 'json') + '\n'), Buffer.byteLength(wire(value, 'toon') + '\n')) / 4); } +/** Heuristic units from the SDK estimator, covering both formats and newline; not tokenizer counts. */ +export function tokens(value: unknown): number { return estimatePmOutputTokens(Math.max(Buffer.byteLength(wire(value, 'json') + '\n'), Buffer.byteLength(wire(value, 'toon') + '\n'))); } /** Accept bounded integral controls without coercing malformed values. */ export function bound(value: number, min: number, max: number): number { requireInput(Number.isSafeInteger(value) && value >= min && value <= max, 'Numeric control is outside its documented bounds'); return value; } /** Resolve a reference name without retaining its value in diagnostics. */ @@ -44,7 +44,14 @@ export function resolveReference(value: string): string { } /** Reject prompt-sized and token-shaped declarations before persistence. */ export function label(value: string): string { requireInput(/^[a-zA-Z][a-zA-Z0-9_-]{0,47}$/.test(value), 'Names must be 1 to 48 identifier characters'); return value.toLowerCase(); } +/** Translate host and fitter failures without exposing source content or SDK diagnostics. */ +export function failure(error: unknown): HttpError { + if (error instanceof HttpError) return error; + if (error instanceof OutputEnvelopeFitError) return new HttpError(error.code, 'A verified HTTP packet could not be fitted', error.code === 'output_envelope_infeasible' ? 'Increase --budget to contain the complete omission receipt' : 'Read complete evidence with pm http missing and the observations SDK; report the fitting refusal'); + if (error instanceof PmCliError && error.exitCode === EXIT_CODE.CONFLICT) return new HttpError('conflict', 'Another writer holds or changed the target', 'Wait for the writer, read current context and explicitly resume with the same operation identity'); + return new HttpError('sdk', 'HTTP context SDK operation failed', 'Check tracker health, access and registered schema; retry the same operation identity'); +} /** Adapt all public operations to safe, typed failures. */ export function guarded(fn: (...args: A) => Promise): (...args: A) => Promise { - return async (...args) => { try { return await fn(...args); } catch (error) { if (error instanceof HttpError) throw error; if (error instanceof PmCliError && error.exitCode === EXIT_CODE.CONFLICT) throw new HttpError('conflict', 'Another writer holds or changed the target', 'Wait for the writer, read current context and explicitly resume with the same operation identity'); throw new HttpError('sdk', 'HTTP context SDK operation failed', 'Check tracker health, access and registered schema; retry the same operation identity'); } }; + return async (...args) => { try { return await fn(...args); } catch (error) { throw failure(error); } }; } diff --git a/src/derive.ts b/src/derive.ts index bd30dea..de67158 100644 --- a/src/derive.ts +++ b/src/derive.ts @@ -1,5 +1,7 @@ import { matches } from './capture.ts'; -import { bound, digest, label, tokens, requireInput } from './core.ts'; +import { fitOutputEnvelope } from '@unbrained/pm-cli/sdk'; +import type { OutputEnvelopeFitReceipt } from '@unbrained/pm-cli/sdk'; +import { bound, digest, label, wire } from './core.ts'; import { Store } from './store.ts'; import type { Contract, Missing, Observation, Options, State, Verdict } from './types.ts'; @@ -78,17 +80,26 @@ export function differences(value: State): unknown[] { return { kind: 'difference', probe: probe.name, mode: firstBad < 0 ? 'latest-pair' : 'first-bad-last-good', before: before === undefined ? null : { id: before.id, at: before.at }, after: after === undefined ? null : { id: after.id, at: after.at }, verdict: after === undefined ? null : verdict(after, contract, digest(probe)), changes }; }); } -/** Build a compact, historical packet; the receipt and both renderer costs count toward budget. */ +/** Construct domain accounting from the SDK fixed point without another convergence loop. */ +function packetEnvelope(task: string, revision: number, rows: readonly unknown[], fit: Readonly) { + const packet = { task, revision, historical: true, rows, estimatedTokens: fit.estimated_tokens, bytes: { json: fit.output_bytes, toon: fit.output_bytes }, receipt: { sourceComplete: true, omitted: fit.omitted_count, returned: rows.length, restore: `pm http diff ${task} --budget 1000000`, missing: `pm http missing ${task}`, estimator: 'ceil(max(json,toon)_utf8_bytes/4)', budgetUnit: 'heuristic-units' } }; + // Both formats spell these scalar integers identically. Their length difference + // is independent of the accounting values, even across decimal digit boundaries. + // Subtract that difference from the SDK's maximum; the SDK alone converges and + // verifies the complete envelope, including these fields and output_fit. + const json = Buffer.byteLength(wire({ ...packet, output_fit: fit }, 'json') + '\n'); + const toon = Buffer.byteLength(wire({ ...packet, output_fit: fit }, 'toon') + '\n'); + const max = Math.max(json, toon); + packet.bytes = { json: Math.max(0, fit.output_bytes - (max - json)), toon: Math.max(0, fit.output_bytes - (max - toon)) }; + return packet; +} +/** Build a historical packet under a self-inclusive heuristic-unit ceiling, not a model-token budget. */ export async function differencePacket(item: string, options: Options & { budget?: number }) { const budget = bound(options.budget ?? 1500, 256, 1000000); const snapshot = await new Store(options).read(item); const source = [...missing(snapshot.value).map(row => ({ kind: 'missing', ...row })), ...differences(snapshot.value)]; - /** Recompute self-inclusive token accounting after every selected row. */ - const make = (rows: unknown[]) => { - const packet = { task: snapshot.id, revision: snapshot.value.revision, historical: true, rows, estimatedTokens: 0, receipt: { sourceComplete: true, omitted: source.length - rows.length, returned: rows.length, restore: `pm http diff ${snapshot.id} --budget 1000000`, missing: `pm http missing ${snapshot.id}`, estimator: 'ceil(max(json,toon)_utf8_bytes/4)' } }; - let cost = tokens(packet); while (packet.estimatedTokens !== cost) { packet.estimatedTokens = cost; cost = tokens(packet); } return packet; - }; - let result = make([]); - requireInput(result.estimatedTokens <= budget, 'Budget cannot contain the complete omission receipt; increase --budget'); - for (const row of source) { const next = make([...result.rows, row]); if (next.estimatedTokens <= budget) result = next; } - return result; + return fitOutputEnvelope(source, { + tokenBudget: budget, policy: 'skip', + buildEnvelope: (rows, receipt) => packetEnvelope(snapshot.id, snapshot.value.revision, rows, receipt), + renderers: { json: packet => wire(packet, 'json') + '\n', toon: packet => wire(packet, 'toon') + '\n' }, + }).envelope; } diff --git a/test/commands.test.ts b/test/commands.test.ts index 237c51a..f123d1b 100644 --- a/test/commands.test.ts +++ b/test/commands.test.ts @@ -28,8 +28,9 @@ test('real native host runs every command without git, in TOON and JSON', async await cli(f.root, f.workspace, ['contract', 'set', f.item, 'api', '--status', '200', '--status', '204', '--json-shape', shape, '--require-header', 'content-type', '--max-latency-ms', '5000']); for (const format of [[], ['--json']]) { const packet = await cli(f.root, f.workspace, ['diff', f.item, '--budget', '1500', ...format]); - const parsed = (format.length === 0 ? decodeToon(packet.stdout) : JSON.parse(packet.stdout)) as { receipt: { sourceComplete: boolean } }; + const parsed = (format.length === 0 ? decodeToon(packet.stdout) : JSON.parse(packet.stdout)) as { bytes: { json: number; toon: number }; receipt: { sourceComplete: boolean } }; assert.ok(parsed.receipt.sourceComplete); assert.ok(Buffer.byteLength(packet.stdout) / 4 <= 1500); + assert.equal(Buffer.byteLength(packet.stdout), format.length === 0 ? parsed.bytes.toon : parsed.bytes.json); } await cli(f.root, f.workspace, ['compare', f.item, 'api']); await cli(f.root, f.workspace, ['missing', f.item, '--json']); const bounded = JSON.parse((await cli(f.root, f.workspace, ['diff', f.item, '--budget', '256', '--json'])).stdout) as { receipt: { restore: string; missing: string } }; diff --git a/test/packet.test.ts b/test/packet.test.ts new file mode 100644 index 0000000..f31e01c --- /dev/null +++ b/test/packet.test.ts @@ -0,0 +1,94 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { addProbe, differencePacket, send, setContract } from '../index.ts'; +import { fitOutputEnvelope } from '@unbrained/pm-cli/sdk'; +import { guarded, HttpError, wire } from '../src/core.ts'; +import { Store } from '../src/store.ts'; +import { contents, server, tracker } from './helpers.ts'; + +/** Independently measure the final public packet, including its accounting and newline. */ +function costs(packet: unknown) { + return Object.fromEntries((['json', 'toon'] as const).map(format => [format, Buffer.byteLength(wire(packet, format) + '\n')])); +} + +test('packet exact bytes include both final renderers, digests, Unicode and all receipts', async t => { + const f = await tracker(t); let status = 200; + const s = await server(t, (_req, res) => { res.writeHead(status, { 'content-type': 'application/json' }); res.end(status === 200 ? '{"revision":1}' : '{"revision":"changed"}'); }); + await addProbe(f.item, 'api', { url: s.url, boundary: s.url }, f.options); + await setContract(f.item, 'api', { statuses: [200] }, f.options); + await send(f.item, 'api', { ...f.options, operationId: 'good' }); status = 503; + await send(f.item, 'api', { ...f.options, operationId: 'bad' }); + const store = new Store(f.options); + await store.change(f.item, value => { value.observations[1].retry.reason = 'é漢字🙂\n'.repeat(9); return true; }, 'Synthetic Unicode legacy evidence'); + const before = await contents(f.workspace, true); + const packet = await differencePacket(f.item, f.options); + const measured = costs(packet); + assert.deepEqual(Reflect.get(packet, 'bytes'), measured, 'packet exposes exact JSON and TOON UTF-8 bytes'); + assert.equal(packet.estimatedTokens, Math.ceil(Math.max(...Object.values(measured)) / 4)); + const fit = Reflect.get(packet, 'output_fit'); + assert.equal(fit.output_bytes, Math.max(...Object.values(measured))); + assert.equal(fit.estimated_tokens, packet.estimatedTokens); assert.equal(fit.policy, 'skip'); + assert.equal(packet.receipt.omitted, 0); assert.equal(packet.receipt.returned, packet.rows.length); + assert.equal(Reflect.get(packet.receipt, 'budgetUnit'), 'heuristic-units'); + assert.match(JSON.stringify(packet), /[a-f0-9]{64}/); assert.match(JSON.stringify(packet), /漢字/); + assert.equal(await contents(f.workspace, true), before, 'fitting is read-only'); +}); + +test('SDK packet fitting retains later small rows after an oversized row and counts omissions exactly', async t => { + const f = await tracker(t); + await addProbe(f.item, 'large', { url: 'https://example.com' }, f.options); + await addProbe(f.item, 'small', { url: 'https://example.com' }, f.options); + let changed = false; + const s = await server(t, (_req, res) => { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify(Object.fromEntries(Array.from({ length: 20 }, (_, i) => [`field${i}`, changed ? 'changed' : i])))); }); + const store = new Store(f.options); + await store.change(f.item, value => { value.probes[0].url = s.url + '/'; value.probes[0].boundary = { origin: s.url, prefix: '/', private: true }; return true; }, 'Synthetic local probe declaration'); + await setContract(f.item, 'large', { statuses: [200] }, f.options); + await send(f.item, 'large', { ...f.options, operationId: 'before' }); changed = true; + await send(f.item, 'large', { ...f.options, operationId: 'after' }); + const full = await differencePacket(f.item, f.options); + const bounded = await differencePacket(f.item, { ...f.options, budget: 400 }); + assert.deepEqual(Reflect.get(bounded, 'bytes'), costs(bounded)); + const fit = Reflect.get(bounded, 'output_fit'); + assert.equal(fit.total_candidates, full.rows.length); + assert.equal(fit.omitted_count, full.rows.length - bounded.rows.length); + assert.equal(bounded.receipt.omitted, fit.omitted_count); + assert.deepEqual(bounded.rows, fit.included_indices.map((index: number) => full.rows[index])); + assert.ok(bounded.estimatedTokens <= 400); + assert.ok(fit.included_indices.includes(full.rows.length - 1)); + assert.ok(!fit.included_indices.includes(full.rows.length - 2)); assert.equal(bounded.receipt.sourceComplete, true); +}); + +test('empty minimum receipts and decimal byte boundaries remain exact with bounded whole rows', async t => { + const f = await tracker(t); + const empty = await differencePacket(f.item, { ...f.options, budget: 256 }); + assert.deepEqual(Reflect.get(empty, 'bytes'), costs(empty)); assert.deepEqual(empty.rows, []); + assert.equal(empty.receipt.omitted, 0); assert.equal(empty.receipt.returned, 0); + assert.ok(empty.estimatedTokens <= 256); + for (let i = 0; i < 20; i++) await addProbe(f.item, `probe${i}`, { url: 'https://example.com' }, f.options); + const full = await differencePacket(f.item, { ...f.options, budget: 9999 }); + assert.ok(full.estimatedTokens >= 1000 && full.estimatedTokens < 9999); + const exact = await differencePacket(f.item, { ...f.options, budget: full.estimatedTokens }); + const smaller = await differencePacket(f.item, { ...f.options, budget: full.estimatedTokens - 1 }); + assert.equal(exact.receipt.omitted, 0); assert.ok(smaller.receipt.omitted > 0); + for (const packet of [full, exact, smaller]) { + assert.deepEqual(Reflect.get(packet, 'bytes'), costs(packet)); + assert.equal(packet.estimatedTokens, Math.ceil(Math.max(...Object.values(costs(packet))) / 4)); + } +}); + + +test('actual SDK infeasible and work-limit refusals remain typed and actionable', async t => { + const f = await tracker(t); const packet = await differencePacket(f.item, f.options); + const renderers = { json: (value: unknown) => wire(value, 'json') + '\n', toon: (value: unknown) => wire(value, 'toon') + '\n' }; + for (const [code, controls] of [ + ['output_envelope_infeasible', { tokenBudget: 1 }], + ['output_envelope_work_limit', { tokenBudget: 1500, maxRenderCalls: 1 }], + ['output_envelope_unstable', { tokenBudget: 1500, maxStabilizationPasses: 1 }], + ] as const) { + await assert.rejects(guarded(async () => fitOutputEnvelope([], { ...controls, buildEnvelope: () => packet, renderers }))(), error => { + assert.ok(error instanceof HttpError); assert.equal(error.code, code); + assert.match(error.remedy, code === 'output_envelope_infeasible' ? /Increase --budget/ : /complete evidence/); + assert.equal(error.message.includes(f.root), false); return true; + }); + } +}); diff --git a/test/real-data.acceptance.ts b/test/real-data.acceptance.ts index 0cfca0d..3357bd3 100644 --- a/test/real-data.acceptance.ts +++ b/test/real-data.acceptance.ts @@ -24,8 +24,8 @@ try { for (let i = 0; i < 10 - publicCalls; i++) { status = i < 4 ? 200 : 503; await send(item.id, 'incident', { ...options, operationId: `incident-${i}` }); } const recorded = (await observations(item.id, options)).observations; const packet = await differencePacket(item.id, options); const missing = await missingEvidence(item.id, options); assert.equal(recorded.length, 10); assert.equal(localCalls + publicCalls, 10); assert.ok(packet.estimatedTokens <= 1500); - const report = { at: new Date().toISOString(), observations: recorded.length, packetTokens: packet.estimatedTokens, packetOmittedRows: packet.receipt.omitted, rawBodyBytes: recorded.reduce((sum, row) => sum + row.bytes, 0), redactions: recorded.reduce((sum, row) => sum + row.receipt.redactions, 0), publicCalls, fixtureCalls: localCalls, calls: localCalls + publicCalls, wallMs: Math.round(performance.now() - started), missing: missing.missing }; - const rawTokens = Math.ceil(report.rawBodyBytes / 4); - const text = `# Real-data proof\n\nMeasured ${report.at}. Ten explicit read-only requests in a temporary non-git SDK tracker, deleted after the run. Public calls used the unauthenticated GitHub REST repository endpoints for public fleet packages; remaining calls used a synthetic local incident. No credentials were sent. Throttling stops further public calls.\n\n| Measure | Result |\n|---|---:|\n| Observations | ${report.observations} |\n| HTTP calls | ${report.calls} (${report.publicCalls} public, ${report.fixtureCalls} fixture) |\n| Captured raw response body bytes | ${report.rawBodyBytes} |\n| Raw body estimated tokens (bytes/4, rounded up) | ${rawTokens} |\n| Difference packet estimated tokens | ${report.packetTokens} |\n| Packet omitted rows | ${report.packetOmittedRows} |\n| Header redactions | ${report.redactions} |\n| Total elapsed milliseconds, including SDK setup and recording | ${report.wallMs} |\n\nPacket costs include the omission receipt and the larger serialized JSON/TOON format. Raw cost includes response bodies only, excluding headers and transcript/request overhead. Body values and keys are never retained. Historical captures are not current health. The incident changes from a successful numeric revision to a failing string revision; the packet identifies the first bad and preceding last good observations. Missing evidence: ${JSON.stringify(report.missing)}. Public schemas wider than 64 properties produce explicit structural omissions.\n`; - await writeFile(new URL('../docs/real-data.md', import.meta.url), text); console.log(JSON.stringify({ ...report, rawEstimatedTokens: rawTokens })); + const report = { at: new Date().toISOString(), observations: recorded.length, packetHeuristicUnits: packet.estimatedTokens, packetJsonBytes: packet.bytes.json, packetToonBytes: packet.bytes.toon, packetOmittedRows: packet.receipt.omitted, rawBodyBytes: recorded.reduce((sum, row) => sum + row.bytes, 0), redactions: recorded.reduce((sum, row) => sum + row.receipt.redactions, 0), publicCalls, fixtureCalls: localCalls, calls: localCalls + publicCalls, wallMs: Math.round(performance.now() - started), missing: missing.missing }; + const rawUnits = Math.ceil(report.rawBodyBytes / 4); + const text = `# Real-data proof\n\nMeasured ${report.at}. Ten explicit read-only requests in a temporary non-git SDK tracker, deleted after the run. Public calls used the unauthenticated GitHub REST repository endpoints for public fleet packages; remaining calls used a synthetic local incident. No credentials were sent. Throttling stops further public calls.\n\n| Measure | Result |\n|---|---:|\n| Observations | ${report.observations} |\n| HTTP calls | ${report.calls} (${report.publicCalls} public, ${report.fixtureCalls} fixture) |\n| Captured raw response body bytes | ${report.rawBodyBytes} |\n| Raw body heuristic units (bytes/4, rounded up) | ${rawUnits} |\n| Difference packet heuristic units (bytes/4, rounded up) | ${report.packetHeuristicUnits} |\n| Packet omitted rows | ${report.packetOmittedRows} |\n| Header redactions | ${report.redactions} |\n| Total elapsed milliseconds, including SDK setup and recording | ${report.wallMs} |\n| Packet JSON UTF-8 bytes including newline | ${report.packetJsonBytes} |\n| Packet TOON UTF-8 bytes including newline | ${report.packetToonBytes} |\n\nThese bytes/4 heuristic units are not tokenizer counts or model-token guarantees. Successful-task scope: tracker init/create, four probe declarations, three contract declarations, ten explicit sends, then observations/differencePacket/missingEvidence reads. There are no automatic request retries; calls and observations are counted above. Setup returns, journal/recording receipts and the other read responses are not measured as serialized end-to-end cost.\n\nPacket costs include the omission receipt and the larger serialized JSON/TOON format. Raw cost includes response bodies only, excluding headers and transcript/request overhead. Body values and keys are never retained. Historical captures are not current health. The incident changes from a successful numeric revision to a failing string revision; the packet identifies the first bad and preceding last good observations. Missing evidence: ${JSON.stringify(report.missing)}. Public schemas wider than 64 properties produce explicit structural omissions.\n`; + await writeFile(new URL('../docs/real-data.md', import.meta.url), text); console.log(JSON.stringify({ ...report, rawHeuristicUnits: rawUnits })); } finally { fixture.closeAllConnections(); await new Promise(resolve => { fixture.close(() => resolve()); }); await rm(root, { recursive: true, force: true }); } diff --git a/test/revert.acceptance.ts b/test/revert.acceptance.ts index 4612045..0c14dc7 100644 --- a/test/revert.acceptance.ts +++ b/test/revert.acceptance.ts @@ -20,7 +20,7 @@ function functionBody(source: string, name: string): { start: number; end: numbe /** Revert exactly a historical function without duplicating its implementation in a fixture. */ function historicalBody(name = 'send', revision = '560e3de'): string { let source: string; - try { source = execFileSync('git', ['show', revision + ':src/' + (name === 'targetDigest' ? 'store' : name === 'capture' ? 'transport' : 'api') + '.ts'], { cwd: repository, env: npmEnvironment(gitEnvironment(process.env)), encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); } + try { source = execFileSync('git', ['show', revision + ':src/' + (name === 'differencePacket' ? 'derive' : name === 'targetDigest' ? 'store' : name === 'capture' ? 'transport' : 'api') + '.ts'], { cwd: repository, env: npmEnvironment(gitEnvironment(process.env)), encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); } catch { throw new Error('Historical revert proof needs the package Git history; run it from a source checkout'); } const body = functionBody(source, name); return source.slice(body.start, body.end); } @@ -56,6 +56,11 @@ const scenarios = [ ['review-comment-journal', 'api.ts', 'send', 'legacy weak-fingerprint', (body: string) => body.replace(" });\n const plannedTarget", " }, note('Journaled explicit HTTP send intent'));\n const plannedTarget").replace(" });\n } catch (error)", " }, note('Marked possible HTTP request effect; retries require explicit identity'));\n } catch (error)")], ['review-request-lifecycle', 'transport.ts', 'capture', 'protocol upgrade variants settle', (_body: string) => historicalBody('capture', '04babaa')], ['review-upgrade-socket', 'transport.ts', 'capture', 'protocol upgrade variants settle', (body: string) => body.replaceAll('socket.destroy();', '').replaceAll('req.destroy();', '')], + ['issue5-packet', 'derive.ts', 'differencePacket', 'packet exact bytes|SDK packet fitting|empty minimum', (_body: string) => historicalBody('differencePacket', 'e2074b1')], + ['issue5-packet-host', 'derive.ts', 'differencePacket', 'real native host runs every command', (_body: string) => historicalBody('differencePacket', 'e2074b1')], + ['issue5-skip', 'derive.ts', 'differencePacket', 'SDK packet fitting', (body: string) => body.replace("policy: 'skip'", "policy: 'prefix'")], + ['issue5-byte-framing', 'derive.ts', 'packetEnvelope', 'packet exact bytes', (body: string) => body.replace("'json') + '\\n'", "'json')")], + ['issue5-sdk-refusals', 'core.ts', 'failure', 'actual SDK infeasible', (body: string) => body.replace('if (error instanceof OutputEnvelopeFitError)', 'if (false)')], ] as const; /** Run one real behavioral test file in a disposable copy, never the reviewed checkout. */ function run(root: string, pattern: string, file: string) { const outcome = spawnSync(process.execPath, ['--test', '--test-concurrency=2', '--test-name-pattern', pattern, `test/${file}.test.ts`], { cwd: root, env: npmEnvironment(process.env), encoding: 'utf8', timeout: 180000 }); return { status: outcome.status, output: outcome.stdout + outcome.stderr }; } @@ -66,8 +71,9 @@ try { const root = join(scratch, name); for (const path of ['src', 'test', 'index.ts', 'package.json']) cpSync(join(repository, path), join(root, path), { recursive: true }); symlinkSync(join(repository, 'node_modules'), join(root, 'node_modules'), 'dir'); const target = join(root, 'src', file); const source = readFileSync(target, 'utf8'); const selected = functionBody(source, functionName); const begin = selected.start; const end = selected.end; const body = source.slice(begin, end); const replaced = mutation(body); assert.notEqual(body, replaced); - writeFileSync(target, source.slice(0, begin) + replaced + source.slice(end)); - const suite = ['review-request-lifecycle', 'review-upgrade-socket'].includes(name) ? 'lifecycle' : name.startsWith('review-comment-') ? 'context-identity' : ['review-policy-frame', 'review-original-send', 'review-reservation-integrity', 'review-stored-declarations', 'review-schema-admission'].includes(name) ? 'guard-policy' : name.startsWith('review-') ? 'admission' : name === 'host-error-privacy' ? 'commands' : name === 'inert-rendering' || name === 'conditional-record' ? 'models' : 'http'; const outcome = run(root, pattern, suite); assert.equal(outcome.status, 1, `${name} must fail by assertion, not timeout or crash`); + writeFileSync(target, (name === 'issue5-packet' || name === 'issue5-packet-host' ? "import { tokens, requireInput } from './core.ts';\n" : '') + source.slice(0, begin) + replaced + source.slice(end)); + const suite = name === 'issue5-packet-host' ? 'commands' : name.startsWith('issue5-') ? 'packet' : ['review-request-lifecycle', 'review-upgrade-socket'].includes(name) ? 'lifecycle' : name.startsWith('review-comment-') ? 'context-identity' : ['review-policy-frame', 'review-original-send', 'review-reservation-integrity', 'review-stored-declarations', 'review-schema-admission'].includes(name) ? 'guard-policy' : name.startsWith('review-') ? 'admission' : name === 'host-error-privacy' ? 'commands' : name === 'inert-rendering' || name === 'conditional-record' ? 'models' : 'http'; const outcome = run(root, pattern, suite); assert.equal(outcome.status, 1, `${name} must fail by assertion, not timeout or crash`); + if (name === 'issue5-packet') assert.match(outcome.output, /fail 3/, 'Every new packet regression fails with the original function'); writeFileSync(target, source); const restored = run(root, pattern, suite); assert.equal(restored.status, 0, `${name} fixed behavior must pass after restoration: ${restored.output}`); console.log(`${name}: safety removal fails; restored implementation passes`); }