mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-07 03:00:29 +00:00
## Summary
Removes `Principal::Anonymous` as an actor variant and makes run
creator/provenance non-optional across the full stack — Rust types,
OpenAPI schema, generated TypeScript client, and frontend components.
Every persisted run now has a mandatory creator; there are no nullable
`created_by`, `provenance`, or `subject` fields anywhere in the system.
As a separate cleanup, all `cargo` invocations in CI and dev tooling
gain `--locked` to prevent unintended dependency resolution drift.
### Plan Summary
- **Commit 1 – Remove `Principal::Anonymous`**:
`RequestAuthContext.principal` becomes `Option<Principal>`; `initial()`
and `rejected()` set `None`; all auth gate helpers match on
`Option<Principal>`; HTTP log computes `principal_kind` as
`principal.as_ref().map_or("none", Principal::kind)`;
`PrincipalAnonymous` removed from the OpenAPI schema and generated
client; frontend `principalDisplay` drops the `"anonymous"` case.
- **Commit 2 – Total provenance**: `RunProvenance.subject`,
`RunSpec.provenance`, `Run.created_by`, and `RunCreatedProps.provenance`
all become non-optional. `Default` derive dropped from `RunProvenance`.
Demo mode gets a synthetic `DEMO_PRINCIPAL` via `AuthMethod::DevToken`.
A new `fabro-types` `test-support` feature exports `test_principal()`
and `test_run_provenance()` used across all affected crates and TS
tests. All `provenance: None` / `created_by: null` literals in tests are
replaced with the test fixture.
- **`--locked` CI hardening**: Every `cargo` invocation in GitHub
Actions workflows and `fabro-dev` tooling commands now passes
`--locked`.
### Key design decisions
- `None` principal (unauthenticated / rejected requests) is now
structurally distinct from any actor variant — no sentinel value that
could accidentally pass actor checks.
- `RunProvenance` no longer implements `Default` because a provenance
with no subject is incoherent; callers must supply a real actor at
construction time.
- The `cancel_run` handler was refactored as part of this change to
correctly handle in-process blocked runs: a new test
(`cancel_durably_blocked_in_process_run_cancels_pending_interview_without_abort_signal`)
covers the cancellation path that should let the workflow unwind rather
than aborting it.
- Test fixtures live under the existing `test-support` feature flag
pattern; no fake-auth helpers were added to the `fixtures` module.
### Fabro Details
<details>
<summary>Ran 9 stages in 129m 57s for $53.54</summary>
| Stage | Duration | Cost | Retries |
|---|---|---|---|
| start | 0s | – | 0 |
| toolchain | 1s | – | 0 |
| preflight_compile | 2m 10s | – | 0 |
| preflight_lint | 2m 22s | – | 0 |
| implement | 76m 55s | $47.53 | 0 |
| simplify_opus | 14m 46s | $3.21 | 0 |
| simplify_gpt | 2m 26s | $1.04 | 0 |
| verify | 14m 44s | – | 0 |
| fixup | 15m 43s | $1.76 | 0 |
| **Total** | **129m 57s** | **$53.54** | **0** |
</details>
<details>
<summary>Ran <code>ImplementPlan.fabro</code> (11 nodes and 14
edges)</summary>
```dot
digraph ImplementPlan {
graph [
goal="Implement and simplify",
model_stylesheet="
* { model: claude-opus-4-7; }
"
]
rankdir=LR
start [shape=Mdiamond, label="Start"]
exit [shape=Msquare, label="Exit"]
toolchain [label="Toolchain", shape=parallelogram, 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", max_retries=0]
preflight_compile [label="Preflight Compile", shape=parallelogram, script="cargo check -q --workspace 2>&1", max_retries=0]
preflight_lint [label="Preflight Lint", shape=parallelogram, script="cargo +nightly-2026-04-14 clippy -q --workspace --all-targets -- -D warnings 2>&1", max_retries=0]
fix_lints [label="Fix Lints", prompt="The preflight lint step failed. Read the build output from context and fix all clippy lint warnings.", max_visits=3]
implement [label="Implement", prompt="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.", model="gpt-55", reasoning_effort="xhigh"]
simplify_opus [label="Simplify (Opus)", prompt="@prompts/simplify.md"]
simplify_gpt [label="Simplify (GPT-55)", prompt="@prompts/simplify.md", model="gpt-55"]
verify [label="Verify", shape=parallelogram, script="git fetch origin main 2>&1 && git merge --no-edit --no-stat origin/main 2>&1 && cargo +nightly-2026-04-14 fmt --all 2>&1 && cargo dev docs refresh 2>&1 && cargo +nightly-2026-04-14 fmt --check --all 2>&1 && { command -v rg >/dev/null 2>&1 || { echo 'rg is required for verify'; exit 127; }; } && ! rg -n 'AuthMode::Disabled|RunAuthMethod|RunSubjectProvenance|\bActorRef\b|\bActorKind\b|AuthenticatedSubject|AuthenticatedService|AuthorizeRunScoped|AuthorizeRunBlob|AuthorizeStageArtifact|AuthorizeCommandLog|auth_method\s*==\s*\"disabled\"' lib/crates apps lib/packages docs/public/api-reference/fabro-api.yaml 2>&1 && cargo +nightly-2026-04-14 clippy --workspace --all-targets -- -D warnings 2>&1 && cargo nextest run --workspace --status-level slow --profile ci 2>&1 && cargo dev docs check 2>&1 && bun install --frozen-lockfile 2>&1 && (cd apps/fabro-web && bun run typecheck) 2>&1 && (cd apps/fabro-web && bun run test) 2>&1 && (cd lib/packages/fabro-api-client && bun run typecheck) 2>&1 && cargo dev build -- -p fabro-cli --release 2>&1", goal_gate=true, retry_target="fixup"]
fixup [label="Fixup", prompt="The verify step failed. Read the build output from context and fix all format, clippy, Rust test, docs, TypeScript typecheck/test, and build failures.", max_visits=3]
start -> toolchain
toolchain -> preflight_compile [condition="outcome=succeeded"]
toolchain -> exit
preflight_compile -> preflight_lint [condition="outcome=succeeded"]
preflight_compile -> exit
preflight_lint -> implement [condition="outcome=succeeded"]
preflight_lint -> fix_lints
fix_lints -> preflight_lint
implement -> simplify_opus -> simplify_gpt -> verify
verify -> exit [condition="outcome=succeeded"]
verify -> fixup
fixup -> verify
}
```
</details>
⚒️ Generated with [Fabro](https://fabro.sh)
---------
Co-authored-by: fabro-sh-0530[bot] <281434857+fabro-sh-0530[bot]@users.noreply.github.com>
Co-authored-by: Fabro <noreply@fabro.sh>
Co-authored-by: Bryan Helmkamp <19+brynary@users.noreply.github.com>
Co-authored-by: Release Repro <release-repro@example.com>
121 lines
3.9 KiB
Rust
121 lines
3.9 KiB
Rust
use fabro_types::test_support;
|
||
mod auth_harness;
|
||
mod auth_tokens;
|
||
|
||
use assert_cmd::Command;
|
||
pub(crate) use auth_harness::{
|
||
RealAuthHarness, TEST_DEV_TOKEN, complete_login_via_browser, expire_saved_access_token,
|
||
no_redirect_browser_client, run_detached, saved_auth_entry, seed_dev_token_auth,
|
||
};
|
||
pub(crate) use auth_tokens::{TEST_SESSION_SECRET, issue_test_github_jwt, issue_test_worker_jwt};
|
||
use fabro_store::EventEnvelope;
|
||
use fabro_test::{EnvVars, TestContext, preserve_coverage_env};
|
||
use fabro_types::{Graph, RunId, RunSpec, WorkflowSettings};
|
||
|
||
pub(crate) fn run_output_filters(context: &TestContext) -> Vec<(String, String)> {
|
||
let mut filters = context.filters();
|
||
filters.push((r"\b\d+ms\b".to_string(), "[TIME]".to_string()));
|
||
filters.push((
|
||
r"(?m)^(Graph: ).+$".to_string(),
|
||
"${1}[GRAPH_PATH]".to_string(),
|
||
));
|
||
filters
|
||
}
|
||
|
||
pub(crate) fn fatal_error_line(stderr: &[u8]) -> String {
|
||
let stderr = String::from_utf8_lossy(stderr);
|
||
console::strip_ansi_codes(&stderr)
|
||
.lines()
|
||
.rev()
|
||
.find_map(|line| {
|
||
line.strip_prefix("error: ")
|
||
.or_else(|| line.trim_start().strip_prefix("× "))
|
||
.map(ToOwned::to_owned)
|
||
})
|
||
.expect("stderr should contain a fatal error line")
|
||
}
|
||
|
||
pub(crate) fn unique_run_id() -> String {
|
||
RunId::new().to_string()
|
||
}
|
||
|
||
pub(crate) fn run_projection_json(run_id: &str, status: &serde_json::Value) -> serde_json::Value {
|
||
let run_id = run_id.parse::<RunId>().expect("test run id should parse");
|
||
let spec = RunSpec {
|
||
run_id,
|
||
settings: WorkflowSettings::default(),
|
||
graph: Graph::new("Remote Workflow"),
|
||
graph_source: None,
|
||
workflow_slug: Some("remote-workflow".to_string()),
|
||
automation: None,
|
||
source_directory: Some("/srv/repo".to_string()),
|
||
labels: std::collections::HashMap::default(),
|
||
provenance: test_support::test_run_provenance(),
|
||
manifest_blob: None,
|
||
definition_blob: None,
|
||
git: None,
|
||
fork_source_ref: None,
|
||
};
|
||
|
||
serde_json::json!({
|
||
"spec": serde_json::to_value(spec).expect("run spec should serialize"),
|
||
"start": null,
|
||
"status": status,
|
||
"status_updated_at": "2026-04-05T12:00:01Z",
|
||
"last_event_at": "2026-04-05T12:00:01Z",
|
||
"pending_control": null,
|
||
"checkpoints": [],
|
||
"conclusion": null,
|
||
"sandbox": null,
|
||
"pull_request": null,
|
||
"superseded_by": null,
|
||
"pending_interviews": {},
|
||
"stages": {}
|
||
})
|
||
}
|
||
|
||
pub(crate) fn parse_event_envelopes(response: &serde_json::Value) -> Vec<EventEnvelope> {
|
||
response["data"]
|
||
.as_array()
|
||
.expect("event list response should contain a data array")
|
||
.iter()
|
||
.cloned()
|
||
.map(serde_json::from_value)
|
||
.collect::<Result<Vec<_>, _>>()
|
||
.expect("wire event envelope list should parse")
|
||
}
|
||
|
||
pub(crate) struct LightweightCli {
|
||
home_dir: tempfile::TempDir,
|
||
}
|
||
|
||
impl LightweightCli {
|
||
pub(crate) fn new() -> Self {
|
||
Self {
|
||
home_dir: tempfile::tempdir().expect("temp home dir should exist"),
|
||
}
|
||
}
|
||
|
||
pub(crate) fn home(&self) -> &std::path::Path {
|
||
self.home_dir.path()
|
||
}
|
||
|
||
#[expect(
|
||
clippy::disallowed_methods,
|
||
reason = "Lightweight CLI test harness reconstructs a minimal process env for subprocesses."
|
||
)]
|
||
pub(crate) fn command(&self) -> Command {
|
||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_fabro"));
|
||
cmd.env_clear();
|
||
preserve_coverage_env!(cmd);
|
||
if let Some(path) = std::env::var_os(EnvVars::PATH) {
|
||
cmd.env(EnvVars::PATH, path);
|
||
}
|
||
cmd.env(EnvVars::HOME, self.home_dir.path());
|
||
cmd.env(EnvVars::NO_COLOR, "1");
|
||
cmd.env(EnvVars::FABRO_NO_UPGRADE_CHECK, "true")
|
||
.env(EnvVars::FABRO_HTTP_PROXY_POLICY, "disabled");
|
||
cmd.current_dir(self.home_dir.path());
|
||
cmd
|
||
}
|
||
}
|