threadwire 0.1.5 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/docs/card-10520-plan.md +45 -0
- package/docs/container-runtime.md +3 -2
- package/docs/delegated-result-protocol.md +161 -0
- package/docs/evidence-artifacts.md +106 -0
- package/package.json +1 -1
- package/scripts/verify-package.js +7 -1
- package/src/cli.js +250 -29
- package/src/context-budget-metrics.js +180 -0
- package/src/delegated-result-admission.js +377 -0
- package/src/evidence-store.js +1472 -0
- package/src/notice-queue.js +22 -7
- package/src/providers/opencode.js +42 -18
- package/src/relay.js +6 -6
- package/src/run-worker.js +158 -40
- package/src/telegram-ingress/command.js +16 -0
- package/src/telegram-ingress/config.js +89 -11
- package/src/telegram-ingress/core.js +162 -90
- package/src/telegram-ingress/http.js +17 -1
- package/src/telegram-webhook.js +16 -0
- package/src/types.js +2 -2
- package/src/worker-control.js +294 -0
- package/src/hermes-protocol.js +0 -126
package/README.md
CHANGED
|
@@ -71,15 +71,17 @@ Pass `--resume-session <exact-provider-session-id>` to continue a stored Codex,
|
|
|
71
71
|
|
|
72
72
|
Provider stdout is decoded as JSONL with a finite 1 MiB UTF-8 byte limit for each pending unterminated record. Exceeding it rejects the run, removes listeners and signal handlers, and terminates the provider without parsing or rendering the oversized content.
|
|
73
73
|
|
|
74
|
-
For normal `threadwire run` execution, Threadwire
|
|
74
|
+
For normal `threadwire run` execution, Threadwire stdout is the parent-model admission plane. It emits exactly one versioned terminal `delegated_result` JSON object and no progress records. The envelope contains a bounded, credential-redacted, control-normalized final conclusion when one was produced, structured terminal state and exit code, and the exact bounded provider session handle when available. Its closed schema can also represent concise blocker/decision requests, opaque artifact handles, commit/URL references, and a concise validation summary; current provider adapters do not infer these optional fields from prose. Unknown, nested, wrongly typed, cardinality-invalid, or oversized admission candidates fail closed. See [Delegated-result protocol](docs/delegated-result-protocol.md) for the complete schema, limits, and caller obligations.
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
Set the trusted operator setting `THREADWIRE_EVIDENCE_ROOT` for standalone runs to persist the delegated prompt and raw provider stdout/stderr as a scoped file-backed artifact; Telegram ingress uses its dedicated evidence volume automatically. The parent or owning Telegram topic receives only an opaque bearer handle. Retrieve later with `threadwire evidence read` or the authenticated `/evidence` Telegram command and an explicit bounded selector. See [Evidence artifacts](docs/evidence-artifacts.md) for permissions, quotas, retention, recovery, authorization, redaction, privacy, and cleanup behavior.
|
|
77
|
+
|
|
78
|
+
Telegram is the separate user-visible progress plane. Lifecycle notices, diagnostics, tool activity, and assistant streaming continue there independently; `--tool-messages` affects only Telegram. Execution-environment details, provider records and streams, lifecycle progress, diagnostics, tool names/arguments/details/output, stderr, prompts, narration/reasoning, and full transcripts never enter stdout. Threadwire's bounded error diagnostic remains on stderr. Help output is the sole non-run, human-readable stdout mode. This default is an intentional pre-1.0 protocol break from the former mixed Hermes JSONL stream.
|
|
77
79
|
|
|
78
80
|
The Telegram bot token is used only by Threadwire's notifier and both `TELEGRAM_BOT_TOKEN` and `THREADWIRE_TELEGRAM_BOT_TOKEN` are removed from every provider child environment. The three libexec adapters repeat that scrub as defense in depth. Other environment variables, including the providers' own authentication and normal process configuration, are preserved. Outbound Telegram fetch and response-body parsing share a 30-second deadline by default; set `THREADWIRE_TELEGRAM_REQUEST_TIMEOUT_MS` to a positive safe integer no greater than `2147483647` to override it.
|
|
79
81
|
|
|
80
82
|
A single trusted renderer is the only code allowed to enqueue Telegram notices. By default, parsed assistant text is forwarded in order with every nonblank rendered line labeled `[P<n>] Assistant: `; no CLI flag is required. This preserves Markdown, code-block indentation, blank lines, and whitespace attached to nonblank content while preventing assistant text from imitating trusted activity on a later line. Streamed fenced JavaScript blocks (` ```js ` / ` ```javascript `) are instead emitted as independently valid, escaped Telegram HTML code messages: the `[P<n>] Assistant:` label stays above the code block, and long blocks are Unicode-safe chunked without exposing raw fences. A short blank-only stream is suppressed at normal close. Because retention is bounded, a blank-only prefix longer than the buffer limit is emitted in oldest-first chunks while at most one limit's worth remains deferred; a later visible assistant character therefore still reconstructs the input exactly, but an indefinitely blank stream cannot be suppressed indefinitely. With `--tool-messages`, tool activity uses a single edit-in-place status line (silent `🛠 …` on start, edited to begin `✅` on finish); without it, both the send and completion edit are suppressed. Worker start/completion/failure and warning/error diagnostics continue to use fixed labeled templates in either mode. Non-assistant provider fields—including summaries, tool names, metadata IDs, inputs, commands, stderr content, raw JSON, reasoning/thinking, tokens, and retry internals—are not forwarded. When tool messages are enabled, the sole provider-data exception is normalized Codex command-completion output: it is appended only to its original tool status edit as an escaped, bounded, expandable Telegram HTML blockquote after shared credential redaction.
|
|
81
83
|
|
|
82
|
-
Assistant deltas from all provider stream IDs share one ordered sentence-boundary buffer, preserving provider-event order. The buffer has a finite 16,384-character default: a continuously active punctuation-free stream emits and clears Unicode-safe fixed-size chunks instead of growing indefinitely. Structured events flush any earlier assistant tail, including retained leading whitespace, before their own fixed notice. Completed boundaries enter delivery without a deliberate batching delay; messages combine only when already naturally queued behind delivery. Notices remain ordered, size-limited, paced, and retried conservatively when Telegram reports throttling or a transient failure.
|
|
84
|
+
Assistant deltas from all provider stream IDs share one ordered sentence-boundary buffer, preserving provider-event order. The buffer has a finite 16,384-character default: a continuously active punctuation-free stream emits and clears Unicode-safe fixed-size chunks instead of growing indefinitely. Structured events flush any earlier assistant tail, including retained leading whitespace, before their own fixed notice. Completed boundaries enter delivery without a deliberate batching delay; messages combine only when already naturally queued behind delivery. When Telegram briefly falls behind, Threadwire emits a fixed safe notice that the notifier saturated and then coalesces only eligible assistant progress until headroom returns; lifecycle, diagnostics, and tool activity remain ordered and non-droppable. Notices remain ordered, size-limited, paced, and retried conservatively when Telegram reports throttling or a transient failure.
|
|
83
85
|
|
|
84
86
|
The delivery queue has finite defaults of 100 pending notices and 256 KiB of pending UTF-8 text. Message length and capacity settings require positive safe integers; pacing settings require nonnegative safe integers. A notice larger than the byte limit or a limit-plus-one enqueue latches a terminal capacity error, discards queued (not already in-flight) notices, and rejects every later enqueue. Unicode chunks never split a surrogate pair, and every labeled continuation retains its complete process label. If the label cannot fit with content under a configured message length, delivery latches a terminal chunking error before sending. These rules make delivery loss visible and prevent slow or rate-limited transports from consuming unbounded memory.
|
|
85
87
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Card 10520 scoped implementation plan
|
|
2
|
+
|
|
3
|
+
## Existing information flow
|
|
4
|
+
|
|
5
|
+
1. `src/cli.js` reads the prompt, resolves the provider command, and gives both to
|
|
6
|
+
`runWorker`.
|
|
7
|
+
2. `src/run-worker.js` spawns the provider, incrementally frames stdout JSONL,
|
|
8
|
+
calls `onRecord` with each raw provider record, normalizes it through the
|
|
9
|
+
provider parser, and serially calls `onEvent`. Stderr is currently reduced to
|
|
10
|
+
one diagnostic; its bytes are discarded.
|
|
11
|
+
3. `src/cli.js` sends normalized events to `WorkerControl`, which renders bounded
|
|
12
|
+
progress to the explicit Telegram target. `ActivityLog` separately records
|
|
13
|
+
only workspace provenance, PID, and resumable session identifiers.
|
|
14
|
+
4. The same normalized stream reaches `DelegatedResultAdmission`, but only
|
|
15
|
+
assistant text is accumulated. On termination, admission writes exactly one
|
|
16
|
+
bounded `delegated_result` JSON object to stdout. That stdout object is the
|
|
17
|
+
parent-facing result; tool/provider records are not emitted there.
|
|
18
|
+
|
|
19
|
+
The current boundary therefore avoids most raw-event admission, but detailed
|
|
20
|
+
prompt/provider/tool/stderr evidence is discarded or reduced and cannot be
|
|
21
|
+
retrieved through an opaque, scoped contract.
|
|
22
|
+
|
|
23
|
+
## Ownership and implementation
|
|
24
|
+
|
|
25
|
+
`EvidenceStore` is the sole persistence and retrieval owner. `runWorker`
|
|
26
|
+
remains the streaming/process-lifecycle owner and exposes raw stdout and stderr
|
|
27
|
+
chunks to one evidence sink with its existing serial backpressure. `cli.js` and
|
|
28
|
+
authenticated Telegram `dispatchWorker()` each own one run-scoped artifact and
|
|
29
|
+
finalize it with the worker lifecycle. CLI admission and the owning Telegram
|
|
30
|
+
topic receive only the bearer handle. `ActivityLog` does not duplicate evidence.
|
|
31
|
+
|
|
32
|
+
Work proceeds RED-GREEN-REFACTOR:
|
|
33
|
+
|
|
34
|
+
1. Add failing store and real CLI-pipeline tests proving the current retrieval
|
|
35
|
+
gap and specifying opaque IDs, permissions, atomic lifecycle, quotas,
|
|
36
|
+
recovery, cleanup, scope isolation, bounded reads, UTF-8/binary behavior, and
|
|
37
|
+
streaming redaction.
|
|
38
|
+
2. Implement the pinned file-backed store and authenticated CLI/Telegram bearer
|
|
39
|
+
retrieval boundaries, then connect both real provider/worker paths without
|
|
40
|
+
changing existing terminal/error semantics.
|
|
41
|
+
3. Add deterministic concurrency, expiry, corruption, filesystem-attack, and
|
|
42
|
+
tool-heavy end-to-end coverage; document operation and run every repository
|
|
43
|
+
check before a full diff/security audit and commit.
|
|
44
|
+
|
|
45
|
+
Card 10521 metrics and admission-policy expansion are explicitly out of scope.
|
|
@@ -97,8 +97,9 @@ node bin/threadwire.js run \
|
|
|
97
97
|
--prompt 'Inspect the container worktree.'
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
Provider activity
|
|
101
|
-
|
|
100
|
+
Provider activity persists at `/var/lib/threadwire/activity`, detailed
|
|
101
|
+
delegated evidence persists privately at `/var/lib/threadwire/evidence`, and
|
|
102
|
+
Codex state persists at `/home/threadwire/.codex`. The Codex adapter reads only
|
|
102
103
|
`OPENAI_API_KEY_FILE`, exports the provider credential immediately before
|
|
103
104
|
`exec`, removes its pointer, and removes every ingress value and pointer from
|
|
104
105
|
the provider environment. Threadwire repeats ingress filtering before spawn.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Delegated-result admission protocol
|
|
2
|
+
|
|
3
|
+
Threadwire has two deliberately separate output planes:
|
|
4
|
+
|
|
5
|
+
- Telegram is the user-visible progress plane. It may receive assistant streaming,
|
|
6
|
+
lifecycle notices, diagnostics, and opt-in tool status.
|
|
7
|
+
- Standard output for a normal `threadwire run` is the parent-model admission
|
|
8
|
+
plane. It contains exactly one newline-terminated terminal JSON envelope.
|
|
9
|
+
|
|
10
|
+
Standard error remains a caller diagnostic channel and is never admission
|
|
11
|
+
content. Provider stdout is private parser input.
|
|
12
|
+
|
|
13
|
+
## Context-budget metrics
|
|
14
|
+
|
|
15
|
+
Each delegated run emits one concise `threadwire-context-metrics` JSON record
|
|
16
|
+
on standard error. It contains content-free integer counters only. Detailed
|
|
17
|
+
fixed-class counters are appended to the run's existing file-backed evidence
|
|
18
|
+
artifact when evidence is configured; they are never added to the admission
|
|
19
|
+
envelope.
|
|
20
|
+
|
|
21
|
+
Byte counters are UTF-8 byte counts. Event and handle counters are counts.
|
|
22
|
+
`estimatedTokens` is `ceil(admittedBytes / 4)` and is explicitly an estimate,
|
|
23
|
+
not provider tokenization. Every counter saturates at 2,147,483,647. Metric
|
|
24
|
+
classes and envelope-field names are closed, fixed sets, so worker-controlled
|
|
25
|
+
values cannot create labels or cardinality. Counters retain no child text,
|
|
26
|
+
commands, arguments, output, secrets, handles, reference values, or other raw
|
|
27
|
+
values.
|
|
28
|
+
|
|
29
|
+
The raw-child counters separately measure provider stdout/stderr stream chunks
|
|
30
|
+
and bytes at the child stream boundary, and successfully JSON-parsed provider
|
|
31
|
+
records at the parser boundary. A parsed record is not an OS chunk: one chunk
|
|
32
|
+
may contain multiple records and one record may span chunks. Rejected counters
|
|
33
|
+
distinguish provider stream, stderr, and lifecycle, diagnostic, and tool
|
|
34
|
+
progress excluded from admission.
|
|
35
|
+
|
|
36
|
+
Progress has fixed `attempted`, `suppressed`, `coalesced`, and `delivered`
|
|
37
|
+
classes. Attempted sizes are JSON-encoded normalized-event bytes. Suppressed
|
|
38
|
+
counts progress disabled by policy, while coalesced counts rendered assistant
|
|
39
|
+
notices merged under notifier saturation. Delivered alone means a rendered
|
|
40
|
+
message or edit was successfully sent; its size is the exact UTF-8 size of the
|
|
41
|
+
JSON-encoded transport value. Consequently batching, rendering, and escaping
|
|
42
|
+
may make delivered sizes differ from attempted sizes.
|
|
43
|
+
|
|
44
|
+
Artifact counters measure finalized logical payload bytes and admitted
|
|
45
|
+
opaque-handle count. When detailed metrics are the last event in that same
|
|
46
|
+
artifact, `artifacts.bytes` is a fixed-point projection that includes the
|
|
47
|
+
newline-terminated metrics JSON itself. Finalization must equal the projection
|
|
48
|
+
or the run fails closed. This avoids a second metrics artifact while giving the
|
|
49
|
+
stored record exact self-inclusive semantics.
|
|
50
|
+
|
|
51
|
+
`admitted.bytes` and `parentContextDeltaBytes` measure the exact UTF-8 bytes of
|
|
52
|
+
the newline-terminated terminal envelope written to stdout. Admitted field
|
|
53
|
+
sizes are the UTF-8 sizes of each field's JSON-encoded value, including string
|
|
54
|
+
quotes, escapes, and multibyte encoding.
|
|
55
|
+
|
|
56
|
+
The regression invariant is exact: given quiet and tool-heavy runs with the
|
|
57
|
+
same terminal conclusion, validation, references, and artifact handles, every
|
|
58
|
+
admitted semantic field is identical and:
|
|
59
|
+
|
|
60
|
+
```text
|
|
61
|
+
heavy.parentContextDeltaBytes
|
|
62
|
+
<= quiet.parentContextDeltaBytes + 256
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The 256-byte allowance is fixed metadata headroom and is independent of raw
|
|
66
|
+
event volume. The current version adds no per-run metric metadata to parent
|
|
67
|
+
context, so equivalent envelopes normally have equal deltas. Tests deliberately
|
|
68
|
+
inject a provider-transcript-shaped conclusion large enough to exceed the
|
|
69
|
+
allowance; this makes accidental stream admission fail deterministically.
|
|
70
|
+
|
|
71
|
+
## Version 1 envelope
|
|
72
|
+
|
|
73
|
+
The closed envelope has these required fields:
|
|
74
|
+
|
|
75
|
+
```json
|
|
76
|
+
{
|
|
77
|
+
"version": 1,
|
|
78
|
+
"type": "delegated_result",
|
|
79
|
+
"state": "completed",
|
|
80
|
+
"exitCode": 0
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`state` is exactly one of `completed`, `failed`, `blocked`, or
|
|
85
|
+
`needs_decision`. `exitCode` is a nonnegative safe integer. The CLI currently
|
|
86
|
+
produces `completed` for exit code zero and `failed` otherwise.
|
|
87
|
+
|
|
88
|
+
The only optional fields are:
|
|
89
|
+
|
|
90
|
+
| Field | Shape | Limit |
|
|
91
|
+
| --- | --- | --- |
|
|
92
|
+
| `conclusion` | string | 16,384 Unicode code points |
|
|
93
|
+
| `blocker` | string | 1,024 Unicode code points |
|
|
94
|
+
| `decisionRequest` | string | 1,024 Unicode code points |
|
|
95
|
+
| `continuationHandle` | opaque ASCII provider token | 512 characters |
|
|
96
|
+
| `artifactHandles` | array of opaque ASCII IDs or safe POSIX-style paths | 16 entries; 512 characters each |
|
|
97
|
+
| `references` | array of `{kind,value}` | 16 entries; 2,048 code points per value |
|
|
98
|
+
| `validationSummary` | string | 2,048 Unicode code points |
|
|
99
|
+
|
|
100
|
+
A reference `kind` is exactly `commit` or `url`. Commit values are 7–64
|
|
101
|
+
hexadecimal characters. URL values are non-whitespace HTTP or HTTPS URLs
|
|
102
|
+
without embedded credentials.
|
|
103
|
+
Artifact handles are opaque identifiers only; artifact content is forbidden.
|
|
104
|
+
The complete UTF-8 JSON envelope is limited to 98,304 bytes.
|
|
105
|
+
|
|
106
|
+
Threadwire preserves accepted continuation handles, artifact handles, and
|
|
107
|
+
references exactly. Streamed conclusion collection is credential-redacted,
|
|
108
|
+
control-normalized, and deterministically truncated with a final ellipsis when
|
|
109
|
+
necessary. Code-point accounting never splits a UTF-16 surrogate pair.
|
|
110
|
+
|
|
111
|
+
A continuation handle consists of one or more colon-separated opaque segments.
|
|
112
|
+
Each segment must start with an ASCII letter or digit and may then contain only
|
|
113
|
+
ASCII letters, digits, `_`, `-`, and `.`. This covers provider UUIDs, token IDs
|
|
114
|
+
such as `ses_123`, and namespaced IDs such as `provider:session-123` without
|
|
115
|
+
admitting prose. Empty segments and known URL/transport scheme prefixes are
|
|
116
|
+
forbidden. An artifact handle uses a single segment's alphabet and may
|
|
117
|
+
additionally use `/` separators with one optional leading `/`; empty, `.` and
|
|
118
|
+
`..` path components are forbidden. Both handle types reject whitespace,
|
|
119
|
+
backslashes, controls (including C0, C1, and ESC), URL forms, userinfo, query
|
|
120
|
+
strings, and fragments.
|
|
121
|
+
|
|
122
|
+
## Fail-closed rules
|
|
123
|
+
|
|
124
|
+
Admission is an explicit allowlist, not a projection of arbitrary provider
|
|
125
|
+
data. A candidate is rejected if it:
|
|
126
|
+
|
|
127
|
+
- has an unknown field or terminal state;
|
|
128
|
+
- uses a wrong or nested type, including an object mislabeled as an allowed
|
|
129
|
+
string or opaque handle;
|
|
130
|
+
- adds fields inside a reference;
|
|
131
|
+
- has an empty, oversized, or over-cardinality optional collection;
|
|
132
|
+
- contains an invalid commit or URL reference; or
|
|
133
|
+
- exceeds a per-field or aggregate limit.
|
|
134
|
+
|
|
135
|
+
Unknown normalized worker event kinds fail the run. Known lifecycle,
|
|
136
|
+
diagnostic, and tool events remain progress-only and cannot affect the
|
|
137
|
+
envelope. Threadwire does not silently remove a forbidden field and admit the
|
|
138
|
+
remainder.
|
|
139
|
+
|
|
140
|
+
## Excluded material
|
|
141
|
+
|
|
142
|
+
The admission envelope never contains execution-environment progress,
|
|
143
|
+
lifecycle progress, diagnostics, provider records or streams, tool names,
|
|
144
|
+
arguments, details, commands or raw output, stderr, prompts,
|
|
145
|
+
narration/reasoning, full transcripts, or artifact content. Optional structured
|
|
146
|
+
fields are populated only from explicit trusted facts; provider prose is not
|
|
147
|
+
parsed to invent them.
|
|
148
|
+
|
|
149
|
+
## Caller obligations and compatibility
|
|
150
|
+
|
|
151
|
+
The caller must parse exactly one envelope, require `version: 1` and
|
|
152
|
+
`type: "delegated_result"`, enforce this closed schema, and reject missing,
|
|
153
|
+
duplicate, trailing, unknown-version, unknown-field, or oversized records
|
|
154
|
+
before parent transcript or context persistence. Standard error and Telegram
|
|
155
|
+
delivery must never be merged into parent context.
|
|
156
|
+
|
|
157
|
+
This protocol is the default and is an intentional pre-1.0 breaking change.
|
|
158
|
+
Callers of the former unversioned mixed progress stream must upgrade before
|
|
159
|
+
using this release. Future file-backed evidence may use opaque
|
|
160
|
+
`artifactHandles`; retrieval and artifact storage are outside version 1 and are
|
|
161
|
+
not implied by a handle.
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# Delegated-worker evidence artifacts
|
|
2
|
+
|
|
3
|
+
Threadwire retains detailed delegated-worker evidence without adding it to the
|
|
4
|
+
parent admission stream. Telegram uses the dedicated
|
|
5
|
+
`/var/lib/threadwire/evidence` volume. A standalone run enables the same store
|
|
6
|
+
with the trusted operator setting `THREADWIRE_EVIDENCE_ROOT`; callers cannot
|
|
7
|
+
select a root through CLI arguments.
|
|
8
|
+
|
|
9
|
+
The store records the prompt and framed provider stdout/stderr events, including
|
|
10
|
+
tool calls, commands, raw results, and intermediate narration the provider
|
|
11
|
+
emits. Threadwire stdout still contains one bounded `delegated_result`; evidence
|
|
12
|
+
adds only an unguessable bearer handle. Storage failure fails the run explicitly
|
|
13
|
+
and never falls back to inlining evidence.
|
|
14
|
+
|
|
15
|
+
## Security and lifecycle
|
|
16
|
+
|
|
17
|
+
The Linux-only store walks an absolute path from a pinned `/` descriptor,
|
|
18
|
+
opening every existing component with `O_DIRECTORY|O_NOFOLLOW`. It accepts
|
|
19
|
+
root-owned non-writable ancestors, root-owned sticky directories such as
|
|
20
|
+
`/tmp`, and same-user directories that are not group/world-writable. Only a missing final component is
|
|
21
|
+
created, relative to its pinned trusted parent, as an owned `0700` directory.
|
|
22
|
+
The store then performs child operations through `/proc/self/fd`; destructive
|
|
23
|
+
work rechecks device/inode. Payload and metadata
|
|
24
|
+
files are `0600`, regular, single-link files. Reconciliation removes only
|
|
25
|
+
recognized implementation-owned artifact and atomic-temporary names. Any
|
|
26
|
+
unknown entry is preserved and makes store opening fail closed.
|
|
27
|
+
|
|
28
|
+
Only one live deployment can open a shared-volume root. The parent opens the
|
|
29
|
+
lock file through the pinned root descriptor and authenticates `/usr/bin/flock`
|
|
30
|
+
through its own descriptor. A synchronous child executes that inherited
|
|
31
|
+
executable descriptor and applies `flock -n` to an inherited duplicate of the
|
|
32
|
+
lock-file open description. The child emits no output and exits; the parent
|
|
33
|
+
retains the locked open description until close or process death.
|
|
34
|
+
|
|
35
|
+
Each event has a bounded kind and length frame, preserving ordering, binary
|
|
36
|
+
data, and split-record provenance without accumulating a transcript in memory.
|
|
37
|
+
Pending evidence counts against quotas, is unreadable, and is not expiry-cleaned
|
|
38
|
+
while active. Fsync and atomic rename publish payload before ready metadata.
|
|
39
|
+
Startup removes partial/corrupt states and rehydrates only exact-schema,
|
|
40
|
+
unexpired ready artifacts.
|
|
41
|
+
|
|
42
|
+
Defaults are 1 MiB/event; 64 MiB and 100,000 events/artifact; 128 MiB and
|
|
43
|
+
200,000 events/run; 1 GiB, 1,000,000 events, and 10,000 artifacts/store;
|
|
44
|
+
seven-day retention; and a 64 KiB/1,000-line serialized-response ceiling.
|
|
45
|
+
Durable reservations include metadata publication overlap and directory-entry
|
|
46
|
+
allowance, so zero-event artifacts consume capacity.
|
|
47
|
+
|
|
48
|
+
## Authorization and retrieval
|
|
49
|
+
|
|
50
|
+
The `evidence_…` handle is a 256-bit bearer capability returned only through the
|
|
51
|
+
owning parent envelope or Telegram topic. Metadata stores its SHA-256 binding,
|
|
52
|
+
not the capability. Artifact redaction policy is AES-GCM sealed under the
|
|
53
|
+
capability with versioned AAD binding every immutable lifecycle/accounting
|
|
54
|
+
field. Ready metadata also contains an HMAC-SHA-256 commitment over the complete
|
|
55
|
+
ordered framed payload, keyed from the bearer without storing the key.
|
|
56
|
+
Ephemeral in-process owner objects are tracked only by weak identity membership;
|
|
57
|
+
completed webhook jobs do not accumulate in an enumerable authorization registry.
|
|
58
|
+
|
|
59
|
+
Telegram retrieval also requires an authenticated webhook update whose
|
|
60
|
+
allowlisted sender and server-derived chat/topic/sender identity match the
|
|
61
|
+
artifact metadata and encrypted-policy AAD. A requester
|
|
62
|
+
cannot submit destination, run, or agent identity. Wrong scope, bad capability,
|
|
63
|
+
missing, corrupt, removed, and expired artifacts all report generic
|
|
64
|
+
unavailability.
|
|
65
|
+
|
|
66
|
+
Parent retrieval syntax:
|
|
67
|
+
|
|
68
|
+
```console
|
|
69
|
+
threadwire evidence read --handle evidence_<bearer> --bytes 2048:512
|
|
70
|
+
threadwire evidence read --handle evidence_<bearer> --lines 40:5
|
|
71
|
+
threadwire evidence read --handle evidence_<bearer> --query "exact command identity" --context-bytes 80
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Authenticated Telegram syntax is
|
|
75
|
+
`/evidence evidence_<bearer> bytes 2048:512` or a bounded `lines` selector in
|
|
76
|
+
the owning chat/topic.
|
|
77
|
+
|
|
78
|
+
Byte offsets are zero-based bytes in the redacted logical stream. Line starts
|
|
79
|
+
are one-based and include terminating newlines. Text boundaries are valid UTF-8;
|
|
80
|
+
binary output is base64. Queries are bounded UTF-8 literal searches—never
|
|
81
|
+
regular expressions. One universal encoded output-byte maximum applies even to
|
|
82
|
+
one huge line. Verification should request the smallest useful excerpt.
|
|
83
|
+
|
|
84
|
+
Retrieval opens the payload once with `O_NOFOLLOW`, verifies the descriptor, and
|
|
85
|
+
in one bounded-memory streaming pass authenticates the exact raw frames while
|
|
86
|
+
parsing, redacting, and retaining only the bounded selection. It drains through
|
|
87
|
+
EOF and releases that selection only after timing-safe commitment verification;
|
|
88
|
+
there is no authenticated-then-reread interval. It does not read whole artifacts
|
|
89
|
+
or build transcript-wide offset arrays. Known runtime secret values, securely read secret-file contents,
|
|
90
|
+
and their pointer values become `[REDACTED]`; responses state whether they were
|
|
91
|
+
redacted or truncated. This finite policy is defense in depth, not
|
|
92
|
+
arbitrary-secret detection.
|
|
93
|
+
|
|
94
|
+
## Operations
|
|
95
|
+
|
|
96
|
+
Run `cleanup()` from maintenance and use scoped removal for revocation. Cleanup
|
|
97
|
+
is idempotent, skips active descriptor-pinned readers, fsyncs the root after
|
|
98
|
+
unlink, and releases capacity only after that transaction succeeds. Trusted
|
|
99
|
+
structured references already approved through the admission boundary remain
|
|
100
|
+
independent of artifact cleanup. No provider currently supplies authenticated
|
|
101
|
+
structured commit or URL metadata, so production does not infer either from
|
|
102
|
+
stdout or ambient repository state.
|
|
103
|
+
|
|
104
|
+
Treat the volume as sensitive provider-transcript data. Exclude it from general
|
|
105
|
+
logs and broad backups. Any required backup must be encrypted and preserve the
|
|
106
|
+
same authorization and retention. Suppressed raw content is never logged.
|
package/package.json
CHANGED
|
@@ -19,7 +19,10 @@ const EXPECTED_FILES = [
|
|
|
19
19
|
"TELEGRAM-INGRESS.md",
|
|
20
20
|
"bin/telegram-webhook.js",
|
|
21
21
|
"bin/threadwire.js",
|
|
22
|
+
"docs/card-10520-plan.md",
|
|
22
23
|
"docs/container-runtime.md",
|
|
24
|
+
"docs/delegated-result-protocol.md",
|
|
25
|
+
"docs/evidence-artifacts.md",
|
|
23
26
|
"package.json",
|
|
24
27
|
"scripts/atomic-install.js",
|
|
25
28
|
"scripts/install-local-launcher.js",
|
|
@@ -31,7 +34,9 @@ const EXPECTED_FILES = [
|
|
|
31
34
|
"scripts/verify-package.js",
|
|
32
35
|
"src/activity-log.js",
|
|
33
36
|
"src/cli.js",
|
|
34
|
-
"src/
|
|
37
|
+
"src/context-budget-metrics.js",
|
|
38
|
+
"src/delegated-result-admission.js",
|
|
39
|
+
"src/evidence-store.js",
|
|
35
40
|
"src/notice-queue.js",
|
|
36
41
|
"src/notifiers/fetch-transport.js",
|
|
37
42
|
"src/notifiers/telegram.js",
|
|
@@ -51,6 +56,7 @@ const EXPECTED_FILES = [
|
|
|
51
56
|
"src/telegram-ingress/update-guard.js",
|
|
52
57
|
"src/telegram-webhook.js",
|
|
53
58
|
"src/types.js",
|
|
59
|
+
"src/worker-control.js",
|
|
54
60
|
"src/workspace-profile.js",
|
|
55
61
|
"threadwire.workspace-profiles.json"
|
|
56
62
|
]
|