Skip to content

Implement pilot allowlist on the upload-claim issuer - #109

Merged
zmanian merged 4 commits into
mainfrom
impl-pilot-allowlist
May 17, 2026
Merged

zmanian merged 4 commits into
mainfrom
impl-pilot-allowlist

Conversation

@zmanian

@zmanian zmanian commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Implements the pilot allowlist designed in #107 and planned in #108. Three slices, four commits, no new direct deps. Off-by-default — deployments that don't set `TRACE_COMMONS_ALLOWLIST_SOURCE` are byte-identical to pre-merge `main`.

  • Slice 1 — `trace_upload_claim_allowlist` module: `hash_invite_code` (sha256:hex(sha256("invite:" + code))), `AllowlistSourceSpec` parser (file works, near reserved-but-rejected), `AllowlistFile/Snapshot/Entry` schema with version pin + strict canonical-lowercase hash validation, `AllowlistSource` trait, `FileAllowlistSource` with refresh-cached snapshots + cache-on-transient-error, `DenialCounter` sliding-window helper. Eight unit tests.
  • Slice 1 — `WorkloadClaims` gains `invite_code: Option`. `UploadClaimClaims` gains `policy_label: Option` with `skip_serializing_if` so existing clients see no JWT-shape change. `IssuerError` gains four PilotAllowlist* refusal vocabulary variants.
  • Slice 2 — `TraceUploadClaimIssuerConfig` gains allowlist + admin-bind fields; `from_env` reads four new env vars (off by default). `build_state` warms the source eagerly (missing/malformed file aborts startup), enforces a loopback guard on `admin_bind` (override via `TRACE_COMMONS_ISSUER_ADMIN_BIND_ALLOW_PUBLIC=1`). New `enforce_pilot_allowlist` helper runs before any other issuance validation, hash-only logs every refusal with the matching `error_class`. Five integration tests cover the 200/403/400/503/back-compat paths.
  • Slice 3 — `trace_upload_claim_issuer_admin` module: `AdminState` + `admin_router()` exposing GET /v1/admin/allowlist-status. Returns counts only; the test suite asserts identity-revealing fields never appear in the response. `serve_trace_upload_claim_issuer` now spawns both axum servers under one shared shutdown signal. `--hash-invite-code ` CLI subcommand uses the same `hash_invite_code` helper so the operator and the issuance handler can never drift. `docs/operator/pilot-allowlist.md` runbook, operator README index row, top-level README "Contributor gate" row.

Refusal vocabulary

Label HTTP Trigger
`PilotAllowlistInviteCodeMissing` 400 Workload claims have no `invite_code`
`PilotAllowlistNotMatched` 403 Hash of supplied invite code not in current snapshot
`PilotAllowlistStale` 503 Cached snapshot older than max-stale window, source not reloading
`PilotAllowlistMalformed` 503 Source returned a parse failure and no cached snapshot
`PilotAllowlistSourceMissing` startup-only File path unreadable at `build_state`
`PilotAllowlistAdminBindNotLoopback` startup-only Admin bind is non-loopback without explicit opt-in
`PilotAllowlistAdminBindFailed` startup-only Admin TCP bind itself fails
`PilotAllowlistNearSourceNotImplemented` startup-only Operator tried the reserved `near:` source variant

New env vars

`TRACE_COMMONS_ALLOWLIST_SOURCE`, `TRACE_COMMONS_ALLOWLIST_REFRESH_INTERVAL_SECONDS` (default 60), `TRACE_COMMONS_ALLOWLIST_MAX_STALE_SECONDS` (default 3600), `TRACE_COMMONS_ISSUER_ADMIN_BIND`, `TRACE_COMMONS_ISSUER_ADMIN_BIND_ALLOW_PUBLIC`.

Test plan

  • `RUSTFLAGS='-D warnings' cargo check -p trace-commons-server --bins` (default)
  • `RUSTFLAGS='-D warnings' cargo check -p trace-commons-server --features near-ai-scorer --bins`
  • `cargo test -p trace-commons-server` (29 allowlist-touching tests green)
  • `cargo clippy -p trace-commons-server --all-targets -- -D warnings` (with the repo's allow-list)
  • `cargo fmt --all -- --check`
  • Operator: walk through `docs/operator/pilot-allowlist.md` against a staging issuer — denial smoke + add-a-contributor mid-pilot + admin endpoint shape.

What's still deferred

  • NEAR-view-call source variant (reserved in CLI/spec; refuses at construction).
  • Ingest server consumption of the `policy_label` JWT claim (issuer emits it; ingest reads it in a later slice when the binding flow lands).
  • Per-policy binding deadlines (operator can ship without them for closed alpha; addable as additive config later).

What this doesn't touch

  • The Ironclaw client. The client + the dev workload-token signer both need to start populating `invite_code` before any allowlisted contributor's first refresh succeeds — runbook calls this out explicitly.
  • The credit-settlement flow. The earned-credits-without-NEAR-account design discussion is a separate slice; the allowlist composes cleanly with all three options outlined in that discussion (the allowlist is the auth boundary, principal_ref + NEAR-binding is downstream).

zmanian added 4 commits May 17, 2026 15:51
…urce

- New trace_upload_claim_allowlist module: hash_invite_code helper
  (sha256:hex(sha256("invite:" + code))), AllowlistSourceSpec parser
  (file:<path> works, near:<account>:<view> reserved-but-rejected),
  AllowlistFile/Snapshot/Entry schema with version pinning and strict
  canonical-lowercase hash validation, AllowlistSource trait,
  FileAllowlistSource with cached + refresh-interval reload + cache-on-
  transient-error semantics, DenialCounter sliding-window helper.
  Eight unit tests cover hashing, source parsing, schema rejection,
  dedupe, refresh-cache, fallback-to-cached on delete/corrupt,
  first-load failure path, and denial-counter window eviction.
- WorkloadClaims gains invite_code: Option<String> (Slice 2 reads it).
- UploadClaimClaims gains policy_label: Option<String> with
  skip_serializing_if so existing clients see no JWT-shape change.
- IssuerError gains the four PilotAllowlist* refusal vocabulary
  variants (NotMatched 403, InviteCodeMissing 400, Stale 503,
  Malformed 503). Marked allow(dead_code) until Slice 2 invokes them.

No behavior change for existing deployments: every new field is
Option<None> on the issuance path; allow-lists are off-by-default.
…ion tests

- TraceUploadClaimIssuerConfig gains allowlist_source / refresh_interval /
  max_stale / admin_bind; from_env reads four new env vars (off by default).
- build_state warms the FileAllowlistSource eagerly so a missing or
  malformed file fails startup, refuses Near sources with the reserved-
  but-not-implemented error label, and enforces a loopback guard on
  admin_bind (override via TRACE_COMMONS_ISSUER_ADMIN_BIND_ALLOW_PUBLIC=1).
- TraceUploadClaimIssuerState carries the optional source + max-stale
  duration + DenialCounter; pub(crate) accessors live on the state for
  Slice 3's admin module to consume.
- New enforce_pilot_allowlist helper runs before any other issuance
  validation: missing invite_code → 400, malformed source → 503, stale
  snapshot → 503, subject_hash not in snapshot → 403 + denial-counter
  increment. All four refusals log hash-only warnings with the matching
  error_class label. Successful match returns the snapshot's policy_label,
  which the minted UploadClaimClaims now carries.
- Five integration tests via the existing post_claim harness: admit-and-
  embed-policy_label, refuse-unlisted, refuse-missing-invite, off-by-
  default-back-compat, stale-snapshot-refusal.

Existing tests untouched. Behavior for deployments that don't set
TRACE_COMMONS_ALLOWLIST_SOURCE is byte-identical to pre-slice main.
- New trace_upload_claim_issuer_admin module: AdminState +
  admin_router() exposing GET /v1/admin/allowlist-status. Returns
  configured / source_label / policy_label / entries (count) /
  snapshot_age_seconds / denials_last_hour / max_stale_seconds / stale.
  Identity-revealing fields (subject_hash, tenant_id, note_label, raw
  invite codes) never appear. Four unit tests with a static fixture
  source confirm both shape and absence-of-identity-fields.
- serve_trace_upload_claim_issuer now builds state once and spawns
  both axum::serve futures (public + optional admin) under one shared
  shutdown signal. The public router is rebuilt via a state-taking
  helper so the admin bind sees the same Arc<State> (same denial
  counter, same allowlist source). The pre-refactor
  serve_router_with_graceful_shutdown helper stays for the existing
  graceful-shutdown test.
- TraceUploadClaimIssuerState gains build_admin_state() so the admin
  module never reaches into private fields.
- trace-commons-upload-claim-issuer binary gains
  --hash-invite-code <CODE>: prints sha256:<...> via the same
  hash_invite_code helper the issuance handler uses. Single source of
  truth between the operator's allowlist file and the runtime check.
- docs/operator/pilot-allowlist.md: full operator runbook —
  what-the-gate-does, how-to-provision-an-invite-code (incl. a
  /dev/urandom one-liner), the JSON schema with field semantics,
  required + optional env vars, reading /v1/admin/allowlist-status,
  denial-smoke procedure, mid-pilot adds, rollback, signer-side
  coordination notes, and known limitations.
- docs/operator/README.md: cross-link row.
- README.md: "Contributor gate" row in the pilot-status table.
- Slice 2's dead_code allows on IssuerError::pilot_allowlist_* are
  now actually-used and removed.

All four CI-style gates green locally: cargo check default,
cargo check --features near-ai-scorer, cargo test, no clippy
warnings. Behavior for deployments that don't set
TRACE_COMMONS_ALLOWLIST_SOURCE remains byte-identical to pre-slice main.
@zmanian
zmanian merged commit 1e738bf into main May 17, 2026
7 checks passed
@zmanian
zmanian deleted the impl-pilot-allowlist branch May 17, 2026 23:45
zmanian added a commit to nearai/ironclaw that referenced this pull request May 19, 2026
Companion to TraceCommons/trace-commons#109 — the trace-commons
server-side invite-code allowlist landed there with refusal labels
PilotAllowlist* (NotMatched 403, InviteCodeMissing 400, Stale 503,
Malformed 503). This commit teaches the Ironclaw trace client three
things:

1. Carry the invite code through the standing policy.
   - StandingTraceContributionPolicy gains
     upload_token_invite_code: Option<String>, serde-default + skip-if-
     none so existing policy files keep parsing byte-identical.
   - TraceUploadClaimIssuerRequest body gains an optional invite_code
     field (forward-compat for a future server slice that may read it
     from the body in addition to the workload-JWT claim).
   - trace_upload_claim_cache_key keys on the invite code so a mid-
     pilot rotation forces a fresh claim mint.

2. Surface the typed allowlist refusal labels.
   fetch_trace_upload_claim_from_issuer now parses {"error": "<Label>"}
   bodies on non-success status and maps each PilotAllowlist* label to
   a user-actionable diagnostic.

3. CLI flag + status surface.
   ironclaw traces opt-in --upload-token-invite-code <CODE> (off by
   default). ironclaw traces status and queue-status report
   "pilot invite code: configured / not configured" — never echo the
   raw code.

After the trace-client extraction (5e568fc + 23104c9), the trace
contribution code lives in crates/ironclaw_reborn_traces. The invite-
code wiring lands on the extracted paths.

Seven new unit tests, all green:
- standing_policy_serde_back_compat_when_invite_code_missing
- standing_policy_serde_round_trips_invite_code_when_set
- standing_policy_serde_omits_invite_code_when_none
- cache_key_distinguishes_different_invite_codes
- parse_trace_upload_claim_error_label_handles_known_shapes
- opt_in_invite_code_flag_parses_through_cli
- opt_in_invite_code_defaults_to_none_when_absent
zmanian added a commit to nearai/ironclaw that referenced this pull request May 20, 2026
Companion to TraceCommons/trace-commons#109 — the trace-commons
server-side invite-code allowlist landed there with refusal labels
PilotAllowlist* (NotMatched 403, InviteCodeMissing 400, Stale 503,
Malformed 503). This commit teaches the Ironclaw trace client three
things:

1. Carry the invite code through the standing policy.
   - StandingTraceContributionPolicy gains
     upload_token_invite_code: Option<String>, serde-default + skip-if-
     none so existing policy files keep parsing byte-identical.
   - TraceUploadClaimIssuerRequest body gains an optional invite_code
     field (forward-compat for a future server slice that may read it
     from the body in addition to the workload-JWT claim).
   - trace_upload_claim_cache_key keys on the invite code so a mid-
     pilot rotation forces a fresh claim mint.

2. Surface the typed allowlist refusal labels.
   fetch_trace_upload_claim_from_issuer now parses {"error": "<Label>"}
   bodies on non-success status and maps each PilotAllowlist* label to
   a user-actionable diagnostic.

3. CLI flag + status surface.
   ironclaw traces opt-in --upload-token-invite-code <CODE> (off by
   default). ironclaw traces status and queue-status report
   "pilot invite code: configured / not configured" — never echo the
   raw code.

After the trace-client extraction (5e568fc + 23104c9), the trace
contribution code lives in crates/ironclaw_reborn_traces. The invite-
code wiring lands on the extracted paths.

Seven new unit tests, all green:
- standing_policy_serde_back_compat_when_invite_code_missing
- standing_policy_serde_round_trips_invite_code_when_set
- standing_policy_serde_omits_invite_code_when_none
- cache_key_distinguishes_different_invite_codes
- parse_trace_upload_claim_error_label_handles_known_shapes
- opt_in_invite_code_flag_parses_through_cli
- opt_in_invite_code_defaults_to_none_when_absent
zmanian added a commit to nearai/ironclaw that referenced this pull request May 22, 2026
Companion to TraceCommons/trace-commons#109 — the trace-commons
server-side invite-code allowlist landed there with refusal labels
PilotAllowlist* (NotMatched 403, InviteCodeMissing 400, Stale 503,
Malformed 503). This commit teaches the Ironclaw trace client three
things:

1. Carry the invite code through the standing policy.
   - StandingTraceContributionPolicy gains
     upload_token_invite_code: Option<String>, serde-default + skip-if-
     none so existing policy files keep parsing byte-identical.
   - TraceUploadClaimIssuerRequest body gains an optional invite_code
     field (forward-compat for a future server slice that may read it
     from the body in addition to the workload-JWT claim).
   - trace_upload_claim_cache_key keys on the invite code so a mid-
     pilot rotation forces a fresh claim mint.

2. Surface the typed allowlist refusal labels.
   fetch_trace_upload_claim_from_issuer now parses {"error": "<Label>"}
   bodies on non-success status and maps each PilotAllowlist* label to
   a user-actionable diagnostic.

3. CLI flag + status surface.
   ironclaw traces opt-in --upload-token-invite-code <CODE> (off by
   default). ironclaw traces status and queue-status report
   "pilot invite code: configured / not configured" — never echo the
   raw code.

After the trace-client extraction (5e568fc + 23104c9), the trace
contribution code lives in crates/ironclaw_reborn_traces. The invite-
code wiring lands on the extracted paths.

Seven new unit tests, all green:
- standing_policy_serde_back_compat_when_invite_code_missing
- standing_policy_serde_round_trips_invite_code_when_set
- standing_policy_serde_omits_invite_code_when_none
- cache_key_distinguishes_different_invite_codes
- parse_trace_upload_claim_error_label_handles_known_shapes
- opt_in_invite_code_flag_parses_through_cli
- opt_in_invite_code_defaults_to_none_when_absent
zmanian added a commit to nearai/ironclaw that referenced this pull request May 23, 2026
Companion to TraceCommons/trace-commons#109 — the trace-commons
server-side invite-code allowlist landed there with refusal labels
PilotAllowlist* (NotMatched 403, InviteCodeMissing 400, Stale 503,
Malformed 503). This commit teaches the Ironclaw trace client three
things:

1. Carry the invite code through the standing policy.
   - StandingTraceContributionPolicy gains
     upload_token_invite_code: Option<String>, serde-default + skip-if-
     none so existing policy files keep parsing byte-identical.
   - TraceUploadClaimIssuerRequest body gains an optional invite_code
     field (forward-compat for a future server slice that may read it
     from the body in addition to the workload-JWT claim).
   - trace_upload_claim_cache_key keys on the invite code so a mid-
     pilot rotation forces a fresh claim mint.

2. Surface the typed allowlist refusal labels.
   fetch_trace_upload_claim_from_issuer now parses {"error": "<Label>"}
   bodies on non-success status and maps each PilotAllowlist* label to
   a user-actionable diagnostic.

3. CLI flag + status surface.
   ironclaw traces opt-in --upload-token-invite-code <CODE> (off by
   default). ironclaw traces status and queue-status report
   "pilot invite code: configured / not configured" — never echo the
   raw code.

After the trace-client extraction (5e568fc + 23104c9), the trace
contribution code lives in crates/ironclaw_reborn_traces. The invite-
code wiring lands on the extracted paths.

Seven new unit tests, all green:
- standing_policy_serde_back_compat_when_invite_code_missing
- standing_policy_serde_round_trips_invite_code_when_set
- standing_policy_serde_omits_invite_code_when_none
- cache_key_distinguishes_different_invite_codes
- parse_trace_upload_claim_error_label_handles_known_shapes
- opt_in_invite_code_flag_parses_through_cli
- opt_in_invite_code_defaults_to_none_when_absent
zmanian added a commit to nearai/ironclaw that referenced this pull request May 23, 2026
… CLI (#3738)

* extract trace client into ironclaw_reborn_traces crate

Move trace_contribution, trace_client, and tools/redaction into a new
workspace crate so the standalone ironclaw_reborn_cli can consume them
without depending on the monolith. The legacy src/trace_*.rs and
src/tools/redaction.rs files remain as thin re-export shims so all
existing call sites in the monolith continue to compile unchanged.

ConversationMessage is now defined in the new crate; src/history/store.rs
re-exports it so crate::history::ConversationMessage and the new
ironclaw_reborn_traces::ConversationMessage are the same type. The single
crate::bootstrap::ironclaw_base_dir() call inside contribution.rs is
rewritten to call ironclaw_common::paths::ironclaw_base_dir directly.

* add traces subcommand stub to ironclaw_reborn_cli

Wire ironclaw_reborn_traces into the standalone reborn CLI and expose a
minimal 'traces' subcommand with opt-in, status, and queue-status. The
opt-in path prints a hand-off message pointing at the legacy ironclaw
binary; status and queue-status call into ironclaw_reborn_traces using
the anonymous scope so the wiring is exercised at compile time. The full
TraceCommons CLI port lands separately.

* wire pilot invite_code through upload-claim refresh

Companion to TraceCommons/trace-commons#109 — the trace-commons
server-side invite-code allowlist landed there with refusal labels
PilotAllowlist* (NotMatched 403, InviteCodeMissing 400, Stale 503,
Malformed 503). This commit teaches the Ironclaw trace client three
things:

1. Carry the invite code through the standing policy.
   - StandingTraceContributionPolicy gains
     upload_token_invite_code: Option<String>, serde-default + skip-if-
     none so existing policy files keep parsing byte-identical.
   - TraceUploadClaimIssuerRequest body gains an optional invite_code
     field (forward-compat for a future server slice that may read it
     from the body in addition to the workload-JWT claim).
   - trace_upload_claim_cache_key keys on the invite code so a mid-
     pilot rotation forces a fresh claim mint.

2. Surface the typed allowlist refusal labels.
   fetch_trace_upload_claim_from_issuer now parses {"error": "<Label>"}
   bodies on non-success status and maps each PilotAllowlist* label to
   a user-actionable diagnostic.

3. CLI flag + status surface.
   ironclaw traces opt-in --upload-token-invite-code <CODE> (off by
   default). ironclaw traces status and queue-status report
   "pilot invite code: configured / not configured" — never echo the
   raw code.

After the trace-client extraction (5e568fc + 23104c9), the trace
contribution code lives in crates/ironclaw_reborn_traces. The invite-
code wiring lands on the extracted paths.

Seven new unit tests, all green:
- standing_policy_serde_back_compat_when_invite_code_missing
- standing_policy_serde_round_trips_invite_code_when_set
- standing_policy_serde_omits_invite_code_when_none
- cache_key_distinguishes_different_invite_codes
- parse_trace_upload_claim_error_label_handles_known_shapes
- opt_in_invite_code_flag_parses_through_cli
- opt_in_invite_code_defaults_to_none_when_absent

* migrate ironclaw traces CLI surface into ironclaw_reborn_cli

Replaces the stub committed in 23104c9 with the full 50-subcommand
implementation moved wholesale from src/cli/traces.rs. The monolith's
ironclaw binary loses its traces subcommand; ironclaw-reborn traces ...
becomes the only path. The library code remains in ironclaw_reborn_traces.

Breaking CLI change: users invoking `ironclaw traces ...` must switch
to `ironclaw-reborn traces ...`. Operator runbooks (pilot bootstrap,
HF cache hygiene, GPU cost ledger) reference the legacy binary name
and will need updating in a follow-up doc PR.

* split traces CLI by audience into module tree

* extract trace CLI tests into sibling tests.rs

* prune operator commands from Ironclaw trace CLI

Architectural pivot: Ironclaw's trace CLI holds only contributor-facing
commands (consent, upload, credit display, monitoring). Operator
commands (reviewer, worker, admin, tenant, audit) move out of Ironclaw
entirely — they'll re-emerge as separate binaries in the trace-commons-
server repo in a follow-up PR.

Deleted 39 variants from TracesSubcommand and everything supporting
them: handler fns, option structs, clap value enums, audience modules
(reviewer.rs/worker.rs/admin.rs/tenant.rs), and tests.

Kept 12 contributor variants: OptIn, OptOut, Status, Preview, Enqueue,
FlushQueue, QueueStatus, Credit, Submit, ListSubmissions, Revoke,
IngestHealth. All 7 invite-code tests continue to pass.

* fix CI: cargo fmt + restore reborn-cli dep boundary

Three CI failures on PR #3738:

1. cargo fmt --check diff in 15 spots across the traces module and tests.
   Ran cargo fmt --all to canonicalize.

2. reborn_crate_dependency_boundaries_hold + reborn_cli_binary_crate_stays_
   separate_from_v1_root failed because the trace-client extraction added
   ironclaw_llm and ironclaw_common as direct deps of ironclaw_reborn_cli
   (the preview handler deserializes ironclaw_llm::recording::TraceFile, and
   the contribution dir resolver called ironclaw_common::paths::ironclaw_
   base_dir). The architectural rule is that ironclaw_reborn_cli enters
   Reborn through the doorway crates only.

   Resolution: re-export both surfaces through ironclaw_reborn_traces:
   - ironclaw_reborn_traces::recording::* re-exports ironclaw_llm::recording
   - ironclaw_reborn_traces::paths::* re-exports ironclaw_common::paths
   Drop ironclaw_llm and ironclaw_common as direct deps of ironclaw_reborn_
   cli. Add ironclaw_reborn_traces to the allowed-doorway set in the
   boundary test, with a doc comment explaining why (traces crate is the
   contributor-side TraceCommons client, analogous to composition + config).

   Final allowed deps of ironclaw_reborn_cli: ironclaw_reborn_composition,
   ironclaw_reborn_config, ironclaw_reborn_traces. No transitive leakage of
   ironclaw_llm / ironclaw_common into the binary's import surface.

Verification:
- RUSTFLAGS="-D warnings" cargo check --workspace --all-targets: clean
- cargo fmt --all -- --check: clean
- cargo test -p ironclaw_architecture --test reborn_dependency_boundaries:
  19 passed
- cargo test -p ironclaw_reborn_cli: 58 passed (invite-code tests included)
- cargo clippy --workspace --all-targets -- -D warnings: clean

* address PR #3738 review feedback

- Enum-ify PilotAllowlist refusal labels (Medium-4).
- Hash invite code in upload-claim cache key with sha256: prefix (Low-2).
- show_policy_status treats whitespace-only invite code as not configured (Low-1).
- Write policy.json with 0o600 permissions on unix (Medium-1).
- Add caller-level tests for PilotAllowlist typed and generic error paths (High-1, High-2).
- Add opt_in invite-code persistence test (Medium-2).
- Add queue-status diagnostics invite-code-configured test (Medium-3).
- Add parse_trace_upload_claim_error_label non-string coverage (Low-3).
theredspoon pushed a commit to theredspoon/ironclaw that referenced this pull request Jun 21, 2026
… CLI (nearai#3738)

* extract trace client into ironclaw_reborn_traces crate

Move trace_contribution, trace_client, and tools/redaction into a new
workspace crate so the standalone ironclaw_reborn_cli can consume them
without depending on the monolith. The legacy src/trace_*.rs and
src/tools/redaction.rs files remain as thin re-export shims so all
existing call sites in the monolith continue to compile unchanged.

ConversationMessage is now defined in the new crate; src/history/store.rs
re-exports it so crate::history::ConversationMessage and the new
ironclaw_reborn_traces::ConversationMessage are the same type. The single
crate::bootstrap::ironclaw_base_dir() call inside contribution.rs is
rewritten to call ironclaw_common::paths::ironclaw_base_dir directly.

* add traces subcommand stub to ironclaw_reborn_cli

Wire ironclaw_reborn_traces into the standalone reborn CLI and expose a
minimal 'traces' subcommand with opt-in, status, and queue-status. The
opt-in path prints a hand-off message pointing at the legacy ironclaw
binary; status and queue-status call into ironclaw_reborn_traces using
the anonymous scope so the wiring is exercised at compile time. The full
TraceCommons CLI port lands separately.

* wire pilot invite_code through upload-claim refresh

Companion to TraceCommons/trace-commons#109 — the trace-commons
server-side invite-code allowlist landed there with refusal labels
PilotAllowlist* (NotMatched 403, InviteCodeMissing 400, Stale 503,
Malformed 503). This commit teaches the Ironclaw trace client three
things:

1. Carry the invite code through the standing policy.
   - StandingTraceContributionPolicy gains
     upload_token_invite_code: Option<String>, serde-default + skip-if-
     none so existing policy files keep parsing byte-identical.
   - TraceUploadClaimIssuerRequest body gains an optional invite_code
     field (forward-compat for a future server slice that may read it
     from the body in addition to the workload-JWT claim).
   - trace_upload_claim_cache_key keys on the invite code so a mid-
     pilot rotation forces a fresh claim mint.

2. Surface the typed allowlist refusal labels.
   fetch_trace_upload_claim_from_issuer now parses {"error": "<Label>"}
   bodies on non-success status and maps each PilotAllowlist* label to
   a user-actionable diagnostic.

3. CLI flag + status surface.
   ironclaw traces opt-in --upload-token-invite-code <CODE> (off by
   default). ironclaw traces status and queue-status report
   "pilot invite code: configured / not configured" — never echo the
   raw code.

After the trace-client extraction (5e568fc + 23104c9), the trace
contribution code lives in crates/ironclaw_reborn_traces. The invite-
code wiring lands on the extracted paths.

Seven new unit tests, all green:
- standing_policy_serde_back_compat_when_invite_code_missing
- standing_policy_serde_round_trips_invite_code_when_set
- standing_policy_serde_omits_invite_code_when_none
- cache_key_distinguishes_different_invite_codes
- parse_trace_upload_claim_error_label_handles_known_shapes
- opt_in_invite_code_flag_parses_through_cli
- opt_in_invite_code_defaults_to_none_when_absent

* migrate ironclaw traces CLI surface into ironclaw_reborn_cli

Replaces the stub committed in 23104c9 with the full 50-subcommand
implementation moved wholesale from src/cli/traces.rs. The monolith's
ironclaw binary loses its traces subcommand; ironclaw-reborn traces ...
becomes the only path. The library code remains in ironclaw_reborn_traces.

Breaking CLI change: users invoking `ironclaw traces ...` must switch
to `ironclaw-reborn traces ...`. Operator runbooks (pilot bootstrap,
HF cache hygiene, GPU cost ledger) reference the legacy binary name
and will need updating in a follow-up doc PR.

* split traces CLI by audience into module tree

* extract trace CLI tests into sibling tests.rs

* prune operator commands from Ironclaw trace CLI

Architectural pivot: Ironclaw's trace CLI holds only contributor-facing
commands (consent, upload, credit display, monitoring). Operator
commands (reviewer, worker, admin, tenant, audit) move out of Ironclaw
entirely — they'll re-emerge as separate binaries in the trace-commons-
server repo in a follow-up PR.

Deleted 39 variants from TracesSubcommand and everything supporting
them: handler fns, option structs, clap value enums, audience modules
(reviewer.rs/worker.rs/admin.rs/tenant.rs), and tests.

Kept 12 contributor variants: OptIn, OptOut, Status, Preview, Enqueue,
FlushQueue, QueueStatus, Credit, Submit, ListSubmissions, Revoke,
IngestHealth. All 7 invite-code tests continue to pass.

* fix CI: cargo fmt + restore reborn-cli dep boundary

Three CI failures on PR nearai#3738:

1. cargo fmt --check diff in 15 spots across the traces module and tests.
   Ran cargo fmt --all to canonicalize.

2. reborn_crate_dependency_boundaries_hold + reborn_cli_binary_crate_stays_
   separate_from_v1_root failed because the trace-client extraction added
   ironclaw_llm and ironclaw_common as direct deps of ironclaw_reborn_cli
   (the preview handler deserializes ironclaw_llm::recording::TraceFile, and
   the contribution dir resolver called ironclaw_common::paths::ironclaw_
   base_dir). The architectural rule is that ironclaw_reborn_cli enters
   Reborn through the doorway crates only.

   Resolution: re-export both surfaces through ironclaw_reborn_traces:
   - ironclaw_reborn_traces::recording::* re-exports ironclaw_llm::recording
   - ironclaw_reborn_traces::paths::* re-exports ironclaw_common::paths
   Drop ironclaw_llm and ironclaw_common as direct deps of ironclaw_reborn_
   cli. Add ironclaw_reborn_traces to the allowed-doorway set in the
   boundary test, with a doc comment explaining why (traces crate is the
   contributor-side TraceCommons client, analogous to composition + config).

   Final allowed deps of ironclaw_reborn_cli: ironclaw_reborn_composition,
   ironclaw_reborn_config, ironclaw_reborn_traces. No transitive leakage of
   ironclaw_llm / ironclaw_common into the binary's import surface.

Verification:
- RUSTFLAGS="-D warnings" cargo check --workspace --all-targets: clean
- cargo fmt --all -- --check: clean
- cargo test -p ironclaw_architecture --test reborn_dependency_boundaries:
  19 passed
- cargo test -p ironclaw_reborn_cli: 58 passed (invite-code tests included)
- cargo clippy --workspace --all-targets -- -D warnings: clean

* address PR nearai#3738 review feedback

- Enum-ify PilotAllowlist refusal labels (Medium-4).
- Hash invite code in upload-claim cache key with sha256: prefix (Low-2).
- show_policy_status treats whitespace-only invite code as not configured (Low-1).
- Write policy.json with 0o600 permissions on unix (Medium-1).
- Add caller-level tests for PilotAllowlist typed and generic error paths (High-1, High-2).
- Add opt_in invite-code persistence test (Medium-2).
- Add queue-status diagnostics invite-code-configured test (Medium-3).
- Add parse_trace_upload_claim_error_label non-string coverage (Low-3).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant