mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-08 03:10:26 +00:00
parent
1b6ac4f04e
commit
cad1fcc115
7 changed files with 552 additions and 9 deletions
382
run.json
382
run.json
File diff suppressed because one or more lines are too long
1
stages/004-preflight_lint@1/output.log
Normal file
1
stages/004-preflight_lint@1/output.log
Normal file
|
|
@ -0,0 +1 @@
|
|||
blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126
|
||||
8
stages/004-preflight_lint@1/script_timing.json
Normal file
8
stages/004-preflight_lint@1/script_timing.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"output": "blob://sha256/12ae32cb1ec02d01eda3581b127c1fee3b0dc53572ed6baf239721a03d82e126",
|
||||
"exit_code": 0,
|
||||
"duration_ms": 153109,
|
||||
"termination": "exited",
|
||||
"output_bytes": 0,
|
||||
"live_streaming": false
|
||||
}
|
||||
6
stages/004-preflight_lint@1/status.json
Normal file
6
stages/004-preflight_lint@1/status.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"outcome": "succeeded",
|
||||
"notes": "Script completed: cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1",
|
||||
"failure_reason": null,
|
||||
"timestamp": "2026-07-01T16:24:40.578472462Z"
|
||||
}
|
||||
129
stages/005-implement@1/prompt.md
Normal file
129
stages/005-implement@1/prompt.md
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
Goal: # Plan A — `SecretRedactor` in `fabro-redact`
|
||||
|
||||
**This is Plan A of three** (split for parallel execution):
|
||||
|
||||
- **Plan A (this file)** — add a per-run secret-value redactor to `fabro-redact`.
|
||||
Self-contained; touches only `fabro-redact/`. **Run in parallel with Plan B.**
|
||||
- **Plan B** — resolve `secrets.*` tokens at the run boundary. **Run in parallel
|
||||
with Plan A.**
|
||||
- **Plan C** — wire redaction across leak surfaces + hooks. **Run after A and B
|
||||
merge** (it consumes this crate's type and Plan B's lookup).
|
||||
|
||||
This plan is inert on its own: it adds a tested library primitive that Plan C
|
||||
wires up. Shipping it alone changes no behavior.
|
||||
|
||||
> **Token notation.** Interpolation tokens are written in this file without their
|
||||
> enclosing double curly braces, so the file is safe to pass directly as a
|
||||
> workflow goal (the goal templater would otherwise try to expand them). Read
|
||||
> `secrets.NAME`, `env.NAME`, and `secrets.*` as the double-curly-brace-wrapped
|
||||
> token form used everywhere else in the codebase, and write the real
|
||||
> double-brace syntax in the code, tests, and docs you produce.
|
||||
|
||||
---
|
||||
|
||||
## Overall goal (shared context)
|
||||
|
||||
Make secret tokens (`secrets.NAME`) in workflow config resolve from the server
|
||||
vault, at the run boundary, with values that never get persisted, never leak, and
|
||||
fail closed when a secret is missing or the wrong type. The redaction guarantee
|
||||
for declared secrets is: content-based redaction (already present) is the
|
||||
universal baseline, plus a per-run registry of resolved secret **values** so a
|
||||
declared secret is redacted even when it does not look like a credential. **This
|
||||
plan builds that registry primitive.**
|
||||
|
||||
Why per-run and not a process global: a test-only in-process run path executes
|
||||
multiple runs in the same process, so redaction state must be per-run, never a
|
||||
`static`/global.
|
||||
|
||||
## Conventions
|
||||
|
||||
- **TDD.** Write the failing test first, then the code.
|
||||
- Match the codebase: Rust import style (types by name, functions via parent
|
||||
module, no glob imports in production), `strum` for enum string maps, keep
|
||||
test-only helpers behind `#[cfg(test)]`.
|
||||
- Plain-English commit messages, PR text, and comments — no internal planning
|
||||
identifiers.
|
||||
- The verify gate runs nightly `fmt --check`, nightly
|
||||
`clippy --all-targets -D warnings`, `cargo nextest run --workspace`, docs check,
|
||||
web/api-client typecheck, and a release build. Implement so all pass.
|
||||
- Never print or log a secret value.
|
||||
|
||||
---
|
||||
|
||||
## Implementation
|
||||
|
||||
### A.1 — Add the `SecretRedactor` type
|
||||
|
||||
File: new `lib/crates/fabro-redact/src/secret_registry.rs`, exported from
|
||||
`lib/crates/fabro-redact/src/lib.rs`.
|
||||
|
||||
Add a cheap, cloneable, per-run registry of secret values that redacts exact
|
||||
matches regardless of shape. It composes *after* the existing content-based
|
||||
redaction (`redact_string`, `redact_json_value`) — this type does not replace
|
||||
them.
|
||||
|
||||
Shape:
|
||||
|
||||
- `SecretRedactor` backed by shared, interior-mutable state (e.g.
|
||||
`Arc<Mutex<Vec<String>>>` or `Arc<RwLock<...>>`) so a clone handed to a
|
||||
different subsystem observes registrations. Derive `Clone` and `Default`; an
|
||||
empty redactor is a pure no-op.
|
||||
- `fn register(&self, value: impl Into<String>)` — store a secret value to be
|
||||
redacted. **Ignore empty or whitespace-only values** (registering an empty
|
||||
string would turn all output into `REDACTED`). De-duplicate.
|
||||
- `fn redact_into(&self, s: &str) -> String` — replace every registered value
|
||||
substring with the same `"REDACTED"` marker used by `redact_string`. Replace
|
||||
**longest values first** so a secret that is a substring of another is handled
|
||||
correctly. If the registry is empty, return the input unchanged (fast path).
|
||||
- `fn redact_json(&self, value: serde_json::Value) -> serde_json::Value` — walk
|
||||
the JSON tree and apply `redact_into` to every string leaf (both object values
|
||||
and array elements; object keys are left as-is). Exact-value matching is
|
||||
unambiguous, so unlike `redact_json_value` this pass does not skip any keys.
|
||||
- Optional `fn is_empty(&self) -> bool` for callers that want to skip work.
|
||||
|
||||
Reuse the crate's existing `"REDACTED"` replacement marker (see `redact_string`
|
||||
in `lib.rs`) rather than introducing a new literal.
|
||||
|
||||
### A.2 — Tests (unit, in the new module)
|
||||
|
||||
- A **low-entropy** value (e.g. `"staging"`) that `redact_string` would *not*
|
||||
catch is replaced with `REDACTED` by `redact_into` after `register("staging")`.
|
||||
- Registering `""` or `" "` is a no-op: `redact_into` leaves unrelated text
|
||||
intact (guard against the empty-value footgun).
|
||||
- Overlapping values: register both `"abc"` and `"abcdef"`; `redact_into` on a
|
||||
string containing `"abcdef"` redacts the whole token (longest-first), not just
|
||||
the `"abc"` prefix.
|
||||
- Empty registry: `redact_into` and `redact_json` are the identity.
|
||||
- `redact_json` redacts a registered value nested inside an object value and
|
||||
inside an array element.
|
||||
- A clone of the redactor observes values registered through the original (shared
|
||||
state), proving it can be handed to another subsystem.
|
||||
|
||||
### A.3 — Verify
|
||||
|
||||
- `cargo +nightly-2026-04-14 fmt --check --all`
|
||||
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings`
|
||||
- `cargo nextest run -p fabro-redact`
|
||||
- release build (`cargo dev build -- -p fabro-cli --release`)
|
||||
|
||||
## Dependencies
|
||||
|
||||
None. Parallel-safe with Plan B. This type is consumed by Plan C.
|
||||
|
||||
|
||||
## Completed stages
|
||||
- **toolchain**: succeeded
|
||||
- Script: `command -v cargo >/dev/null || { curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && sudo ln -sf $HOME/.cargo/bin/* /usr/local/bin/; }; cargo --version 2>&1`
|
||||
- Output:
|
||||
```
|
||||
cargo 1.95.0 (f2d3ce0bd 2026-03-21)
|
||||
```
|
||||
- **preflight_compile**: succeeded
|
||||
- Script: `cargo check -q --workspace 2>&1`
|
||||
- Output: (empty)
|
||||
- **preflight_lint**: succeeded
|
||||
- Script: `cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1`
|
||||
- Output: (empty)
|
||||
|
||||
|
||||
Read the plan file referenced in the goal and implement every step. Make all the code changes described in the plan. Use red/green TDD.
|
||||
6
stages/005-implement@1/provider_used.json
Normal file
6
stages/005-implement@1/provider_used.json
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"mode": "agent",
|
||||
"provider": "openai",
|
||||
"model": "gpt-5.5",
|
||||
"reasoning_effort": "xhigh"
|
||||
}
|
||||
29
stages/005-implement@1/response.md
Normal file
29
stages/005-implement@1/response.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
Implemented Plan A in `fabro-redact`.
|
||||
|
||||
Summary:
|
||||
- Added `SecretRedactor` in `lib/crates/fabro-redact/src/secret_registry.rs`.
|
||||
- Exported it from `lib/crates/fabro-redact/src/lib.rs`.
|
||||
- Reused the crate’s shared `REDACTION_MARKER` constant.
|
||||
- Implemented:
|
||||
- shared cloneable per-run state via `Arc<RwLock<Vec<String>>>`
|
||||
- `register`, ignoring empty/whitespace-only values and de-duplicating
|
||||
- `redact_into`, using longest-first exact-match redaction
|
||||
- `redact_json`, redacting all JSON string leaves while preserving object keys
|
||||
- `is_empty`
|
||||
- Added unit tests covering low-entropy values, empty values, overlaps, empty identity behavior, nested JSON, and shared clone state.
|
||||
|
||||
TDD:
|
||||
- Red step confirmed: `cargo nextest run -p fabro-redact` failed before implementation because `SecretRedactor` was missing.
|
||||
- Green step passed after implementation.
|
||||
|
||||
Verification run:
|
||||
- `cargo +nightly-2026-04-14 fmt --check --all`
|
||||
- `cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings`
|
||||
- `cargo nextest run -p fabro-redact`
|
||||
- `cargo dev build -- -p fabro-cli --release`
|
||||
- `cargo dev docs check`
|
||||
- `cd apps/fabro-web && bun run typecheck`
|
||||
- `cd lib/packages/fabro-api-client && bun run typecheck`
|
||||
- `cargo nextest run --workspace`
|
||||
|
||||
All passed. Note: the first release build attempt failed because Bun workspace dependencies were not installed; I ran `bun install --frozen-lockfile` and reran successfully. No tracked files were changed by that install.
|
||||
Loading…
Add table
Reference in a new issue