{"_id":"@argszero/cordis-plugin-prompt-audit","_rev":"2-5163c257427d7c13c08459d778a6a17e","name":"@argszero/cordis-plugin-prompt-audit","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@argszero/cordis-plugin-prompt-audit","version":"0.1.0","keywords":["cordis","deepseek-harness","dsh","plugin","audit","prompt","observability","provenance","session-log"],"license":"MIT","_id":"@argszero/cordis-plugin-prompt-audit@0.1.0","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"homepage":"https://github.com/argszero/cordis-plugin-prompt-audit#readme","bugs":{"url":"https://github.com/argszero/cordis-plugin-prompt-audit/issues"},"dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"dist":{"shasum":"93cce3a44be81f5de07085fc835baad9ba270b44","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-prompt-audit/-/cordis-plugin-prompt-audit-0.1.0.tgz","fileCount":9,"integrity":"sha512-UH+bdhhSe9R3r5CqgMZXaT8MkpRsCyxhclISVIV4ejYytOPndWR+1g4MSutjI0U20F7HTlrvk5/fsIMYZdxFkg==","signatures":[{"sig":"MEUCIHTZDPR2BscOJDoFuG0qjRz8dGNa8fo0fxWHpuPMHIcmAiEArXKbbacHhcx3SbvX8eE99ITNU7E4exedJkHJMSwfXrY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37861},"main":"lib/index.js","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"8d91794601b55a8a7952903922343d3563d6c0d7","scripts":{"test":"node --test \"test/*.spec.mjs\"","build":"tsc","prepublishOnly":"tsc"},"_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"repository":{"url":"git+https://github.com/argszero/cordis-plugin-prompt-audit.git","type":"git"},"_npmVersion":"11.17.0","description":"Prompt audit for dsh: record a content-addressed digest of every assembled model request — per message (role, id, content hash, author) plus the effective system prompt's identity and hash — to a sidecar file outside the session log. Answers \"was this tex","directories":{},"_nodeVersion":"26.5.0","dependencies":{"@types/node":"^22.20.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"semver":"^7.6.0","typescript":"^5.5.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":"0.1.2-rc.1","@deepseek-ai/dsh-agent":"0.1.2-rc.1","@deepseek-ai/dsh-session":"0.1.2-rc.1","@deepseek-ai/dsh-home-paths":"0.1.2-rc.1"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0","@deepseek-ai/dsh-agent":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0","@deepseek-ai/dsh-session":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0","@deepseek-ai/dsh-home-paths":">=0.1.2-rc.1 <0.2.0 || >=0.1.3-alpha.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0"},"_npmOperationalInternal":{"tmp":"tmp/cordis-plugin-prompt-audit_0.1.0_1789180725610_0.9729818715931327","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"_id":"@argszero/cordis-plugin-prompt-audit@0.1.1","dsh":{"bundle":{"patch":"./cordis.patch.yml"}},"bugs":{"url":"https://github.com/argszero/cordis-plugin-prompt-audit/issues"},"dist":{"shasum":"68590619602e21e331964507c4e1295c475a9a1f","tarball":"https://registry.npmjs.org/@argszero/cordis-plugin-prompt-audit/-/cordis-plugin-prompt-audit-0.1.1.tgz","fileCount":9,"integrity":"sha512-YXT7cyMZ1HiboixOpX7ZTOrZzQUT+jS3T4wlHD5BuFLm3l2/aN+y10mSKusHNR+pC/Mjcv4LwKpjun44/JV16Q==","signatures":[{"sig":"MEUCIQCJwZMTOhHa4rEWK2KJYWZQ/Uf2ip4QJS1EL9l7mOfYaQIgOW2xpuGnty1AwhLzOEu86326iJ8StSOjCx/ITsWwvhw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIEtd5o2Apn9A3FpO6WDpDy05aVaNYWbfZcQ6UZf5aFRTAiEAv9KHJHqCWV2pq/gSpNcgGhvJZz7zYD2OI0kj2DVupUY="}],"unpackedSize":40629},"main":"lib/index.js","name":"@argszero/cordis-plugin-prompt-audit","type":"module","types":"lib/types/index.d.ts","engines":{"node":"^22.19 || >=24"},"exports":{".":{"types":"./lib/types/index.d.ts","default":"./lib/index.js"},"./src/*":"./src/*","./package.json":"./package.json"},"gitHead":"4da220afd617a6c4403dae2db81af726f831d023","license":"MIT","scripts":{"test":"node --test \"test/*.spec.mjs\"","build":"tsc","prepublishOnly":"tsc"},"version":"0.1.1","_npmUser":{"name":"argszero","email":"argszero.reg@gmail.com"},"homepage":"https://github.com/argszero/cordis-plugin-prompt-audit#readme","keywords":["cordis","deepseek-harness","dsh","plugin","audit","prompt","observability","provenance","session-log"],"repository":{"url":"git+https://github.com/argszero/cordis-plugin-prompt-audit.git","type":"git"},"_npmVersion":"11.17.0","description":"Prompt audit for dsh: record a content-addressed digest of every assembled model request — per message (role, id, content hash, author) plus the effective system prompt's identity and hash — to a sidecar file outside the session log. Answers \"was this tex","directories":{},"maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"_nodeVersion":"26.5.0","dependencies":{"@types/node":"^22.20.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"semver":"^7.6.0","typescript":"^5.5.0","@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":"0.1.6-alpha.2","@deepseek-ai/dsh-agent":"0.1.6-alpha.2","@deepseek-ai/dsh-session":"0.1.6-alpha.2","@deepseek-ai/dsh-home-paths":"0.1.6-alpha.2"},"peerDependencies":{"@deepseek-ai/cordis":"^4.0.2","@deepseek-ai/dsh-llm":">=0.1.2-rc.1 <0.1.3 || >=0.1.3-alpha.2 <0.1.4 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-alpha.1 <0.2.0","@deepseek-ai/dsh-agent":">=0.1.2-rc.1 <0.1.3 || >=0.1.3-alpha.2 <0.1.4 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-alpha.1 <0.2.0","@deepseek-ai/dsh-session":">=0.1.2-rc.1 <0.1.3 || >=0.1.3-alpha.2 <0.1.4 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-alpha.1 <0.2.0","@deepseek-ai/dsh-home-paths":">=0.1.2-rc.1 <0.1.3 || >=0.1.3-alpha.2 <0.1.4 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-alpha.1 <0.2.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cordis-plugin-prompt-audit_0.1.1_1789715615970_0.14004175346898062"}}},"time":{"created":"2026-09-12T02:38:45.493Z","modified":"2026-09-18T07:13:36.208Z","0.1.0":"2026-09-12T02:38:45.754Z","0.1.1":"2026-09-18T07:13:36.079Z"},"bugs":{"url":"https://github.com/argszero/cordis-plugin-prompt-audit/issues"},"license":"MIT","homepage":"https://github.com/argszero/cordis-plugin-prompt-audit#readme","keywords":["cordis","deepseek-harness","dsh","plugin","audit","prompt","observability","provenance","session-log"],"repository":{"url":"git+https://github.com/argszero/cordis-plugin-prompt-audit.git","type":"git"},"description":"Prompt audit for dsh: record a content-addressed digest of every assembled model request — per message (role, id, content hash, author) plus the effective system prompt's identity and hash — to a sidecar file outside the session log. Answers \"was this tex","maintainers":[{"name":"argszero","email":"argszero.reg@gmail.com"}],"readme":"# @argszero/cordis-plugin-prompt-audit\n\nRecord a content-addressed digest of **every assembled model request** — per\nmessage (role, id, content hash, **author**) plus the effective system prompt's\nidentity and hash — to a sidecar file outside the session log.\n\nIt answers one question from local artifacts:\n\n> Was this text ever delivered to the model as a user message, and if so, who\n> delivered it?\n\nCloses Discussions [#6376](https://github.com/deepseek-ai/deepseek-harness/discussions/6376) /\n[#6377](https://github.com/deepseek-ai/deepseek-harness/discussions/6377) /\n[#6378](https://github.com/deepseek-ai/deepseek-harness/discussions/6378).\n\n## The gap\n\nA reported turn acted on an instruction that no user sent, and nine file edits\nfollowed. The report could show that the turn had exactly one `user/message`\nevent — a two-word \"continue\" — and that no recorded event carried the\ninstruction. What it could not do was separate the two explanations:\n\n1. the harness delivered a user-role message that was **not recorded**, or\n2. the **model fabricated** the instruction from its own prior output.\n\nSeparating them needs the request actually sent at that step. The harness records\nevents only: the stored `request/header` carries provider/model/maxTokens and\ntool schemas, not the prompt, and `$DSH_HOME` has no log directory or audit\nswitch. `session.v3.jsonl.zstd` cannot answer the question, and the model's own\nassistant text is replayed into later requests, so a fabricated instruction looks\nexactly like an instruction.\n\n## What this plugin does\n\n```sh\ndsh plugin add @argszero/cordis-plugin-prompt-audit\n```\n\nIt mounts on `llm/stream`, the seam that carries the finished request, and writes\none JSON line per model call to `<DSH_HOME>/prompt-audit/requests.jsonl`:\n\n```json\n{\"kind\":\"prompt-audit/request\",\"at\":\"...\",\"request\":{\"sessionId\":\"session-1\",\"turn\":1,\"step\":29,\"provider\":\"go\",\"model\":\"deepseek-v4.1-flash\",\"attempts\":1},\n \"system\":{\"chars\":4102,\"digest\":\"sha256:...\"},\n \"messages\":[\n   {\"role\":\"system\",\"id\":\"m0\",\"chars\":4102,\"digest\":\"sha256:...\",\"source\":{\"kind\":\"plugin\",\"plugin\":\"@deepseek-ai/dsh-system-prompt\",...}},\n   {\"role\":\"user\",\"id\":\"m1\",\"chars\":32,\"digest\":\"sha256:...\",\"source\":{\"kind\":\"plugin\",\"plugin\":\"@deepseek-ai/dsh-agent-instructions\",\"form\":\"instructions\",...}},\n   {\"role\":\"user\",\"id\":\"m2\",\"chars\":8,\"digest\":\"sha256:...\",\"source\":{\"kind\":\"user\",...}}],\n \"tools\":{\"count\":36,\"digest\":\"sha256:...\"},\"variables\":\"sha256:...\"}\n```\n\n**Full content is not recorded.** The report asked for a digest precisely because\nit is sufficient, and a digest keeps the sidecar small enough to retain.\n\n### Why the `source` field is the point\n\n`source` answers *who produced this message*, and it is what separates the two\nexplanations. In the record above, `m1` and `m2` are both `role: \"user\"` — but\none was injected by an instruction file and the other is a human. **User-channel\ntext is not by itself evidence of human authorship**, and the log alone cannot\ntell you which is which.\n\n## Answering the question\n\nThe package ships an offline verifier:\n\n```sh\nnode node_modules/@argszero/cordis-plugin-prompt-audit/tools/verify-request.mjs \\\n  <text-file> <sidecar> [session.jsonl[.zstd]]\n```\n\n```\n$ node tools/verify-request.mjs injected.txt requests.jsonl session.jsonl\ndelivered:             YES — 1 matching message(s)\n  delivered by         plugin (@deepseek-ai/dsh-agent-instructions) — role=user id=m1\n    in                 call session-1 turn=1 step=29 attempt=1\n\nin session log:        NO user/message event\n\nVERDICT: delivered to the model with no user/message event behind it.\n         The text reached the request over the user channel without being logged.\n```\n\nPassing the session log turns the answer three-way:\n\n| delivered | in session log | verdict |\n|---|---|---|\n| yes | yes | an ordinary recorded message |\n| yes | **no** | **delivered over the user channel without being logged** |\n| no | — | the model produced it itself |\n\nThe middle row is the one the session log cannot produce on its own.\n\n## How it is recorded\n\nThe record is taken at the same boundary the harness itself uses to assert its\nrequest agrees with its session log. `agent-loop` freezes the request and its\n`messages` array (`agent.ts:604-616`), and its own invariant re-derives the\nhistory on every call and fails when `options.messages` diverges from\n`session.deriveMessages()`. So what this plugin hashes is the authoritative wire\npayload *and* the durable history — not a reconstruction.\n\nEach message's content address is computed over the text the provider receives,\nso a digest match is exact.\n\n## Why a sidecar and not the session log\n\nThe session storage contract rejects unknown event types on append unless the\nevent is declared ignorable, and that declared-ignorable write path is not\nreachable from a plugin. An audit record therefore cannot live in the log\nwithout a core change — and the point is that the audit exists today, with no\ncore change and no flag required.\n\n## Configuration\n\n| option | default | meaning |\n|---|---|---|\n| `path` | `<DSH_HOME>/prompt-audit/requests.jsonl` | sidecar file; one JSON line per call |\n| `dshHome` | `$DSH_HOME`, then `~/.dsh` | home override used when `path` is omitted |\n| `includeSessionless` | `false` | also record hand-built one-shot calls with no session |\n\n```yaml\n- set:\n    - id: prompt-audit\n      config:\n        path: /var/log/dsh/prompts.jsonl\n```\n\n## Failure posture\n\nThe audit is observability, so nothing here may change a model call:\n\n- the listener delegates to the rest of the chain first and **never alters the\n  stream it observes**;\n- digests are computed and the write queued **after** the call is released, on a\n  serialized queue that cannot interleave lines;\n- every filesystem and serialization failure is **logged and swallowed** — if the\n  audit cannot be written, the call still happens;\n- a failed directory creation is **not memoized**, so a transient failure does\n  not disable recording for the rest of the run.\n\n## Limits\n\n- A digest answers *was this exact text delivered* — it is not full-text search,\n  and it cannot match a paraphrase. That is the deliberate trade for a record\n  small enough to keep.\n- `source` is reported verbatim and merge-extensible: an unknown `kind` is\n  recorded as-is (with its full shape addressable by digest) rather than\n  interpreted.\n- The record covers **delivery**, not authorization. It shows what the model was\n  sent; whether the text *should* have been sent is a policy question it does\n  not answer.\n\n## Compatibility\n\nRequires dsh within the `0.1.x` line, admitted by this peer range (declared\nidentically on `@deepseek-ai/dsh-agent`, `@deepseek-ai/dsh-home-paths`,\n`@deepseek-ai/dsh-llm` and `@deepseek-ai/dsh-session`):\n\n```\n>=0.1.2-rc.1 <0.1.3 || >=0.1.3-alpha.2 <0.1.4 || >=0.1.5-alpha.1 <0.2.0 || >=0.1.6-alpha.1 <0.2.0\n```\n\nEvery dsh release published today is a **prerelease** (`0.1.2-rc.1`, `0.1.5-alpha.1`,\n`0.1.6-alpha.2`, …), and a semver comparator admits a prerelease only when some\ncomparator **in the same group** shares its `major.minor.patch` tuple. Two\nconsequences follow, and both have already bitten this package:\n\n```jsonc\n// Matches nothing: 0.1.2-rc.1 sorts BELOW 0.1.2, and every other prerelease\n// carries a different tuple.  -> ETARGET, the package cannot be installed.\n\">=0.1.2\"\n\n// Only the 0.1.2-rc tuple. The `<0.2.0` upper bound is INERT for prereleases:\n// it excludes no later line, so every other line gets ERESOLVE.\n\">=0.1.2-rc.1 <0.2.0\"\n```\n\nThe second form is the dangerous one, because it *reads* as though it covered\neverything from `0.1.2-rc.1` onward. It does not — `<0.2.0` never excludes\n`0.1.6-alpha.2`, and no comparator names that tuple. Up to **v0.1.0** the\nshipped range was\n\n```\n>=0.1.2-rc.1 <0.2.0 || >=0.1.5-alpha.1 <0.2.0\n```\n\nwhich admitted the `0.1.2-rc` and `0.1.5` tuples and **nothing else** — 5 of the\n23 published versions. A user on the newest shipped dsh release\n(`0.1.6-alpha.2`) therefore could not install the plugin at all:\n\n```\nnpm error ERESOLVE unable to resolve dependency tree\nnpm error peer @deepseek-ai/dsh-llm@\">=0.1.2-rc.1 <0.2.0 || ...\" from\nnpm error   @argszero/cordis-plugin-prompt-audit@0.1.0\n```\n\nThe plugin's own suite passes on that line (31/31). The dsh packages are\n**peers**, so `--legacy-peer-deps` is not something a consumer can reasonably be\nasked to accept: the install simply fails.\n\n**What we ship:** one comparator per supported tuple, each with its own upper\nbound so the intended span is legible rather than implied.\n\n| clause | admits |\n| --- | --- |\n| `>=0.1.2-rc.1 <0.1.3` | `0.1.2-rc.1` |\n| `>=0.1.3-alpha.2 <0.1.4` | `0.1.3-alpha.2` |\n| `>=0.1.5-alpha.1 <0.2.0` | the whole 0.1.5 line (alpha.1, alpha.2, rc.1, rc.2) |\n| `>=0.1.6-alpha.1 <0.2.0` | the whole 0.1.6 line (alpha.1, alpha.2) |\n\n`test/peer-range` **computes** the admitted set with the real `semver` package\nand asserts it equals exactly the set the suite has been run against — 8\nversions — rather than pattern-matching the range string. An earlier guard only\nchecked that the string mentioned `0.1.2-rc.N` and `0.1.5-alpha.N`; that form\ncannot tell a correct range from an incorrect one, which is how the range above\nshipped green. Asserting the set exactly makes both directions loud: dropping a\nsupported line fails, and admitting an unverified line fails too. The same file\nalso asserts that this README quotes the manifest range verbatim.\n\nNode `^22.19 || >=24` (zstd decompression of session logs uses the built-in\n`node:zlib`).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}