Adds run_stdin + create_collection_and_grant fixture helpers and three
acceptance tests for org add card/key/totp. Red until B1/B2 wire the
subcommands (currently: unrecognized subcommand). Asserts org get masks
card number + key material without --show. Edit round-trips land with B3.
- edit_secure_note / edit_key now call the module's resolve_secret_multiline
instead of open-coding the eprintln-hint + read-to-EOF pattern (the helper
exists precisely to centralize this; build_secure_note/build_key already use it).
- drop redundant fn-local imports: `use zeroize::Zeroizing;` from the five edit_*
helpers and the re-imported `TotpAlgorithm` from edit_login/build_login
(all covered by module-level imports; leftover from the verbatim A2/A3 move).
- build_login passes the password_stdin flag through to resolve_secret_line for
consistency with build_card/build_totp (behavior identical — that branch is
only reached when password_stdin is true).
- restore #[allow(clippy::too_many_arguments)] on build_totp (8 args; the old
build_totp_item carried the same allow — signature is frozen for B/C).
A3 routed personal `add` through the shared item_build builders, which use
prompt_secret / resolve_secret_*; the generic single-line prompt() and
prompt_optional() lost their last callers. read_required_line /
read_optional_line stay (used by prompt_or_flag*).
4-stream manual-pane kickoff (no tmux automation): A foundation, B
Card/Key/Totp, C Document+attachments, D server hook. Each dev prompt
mandates a relay polling cadence (read inbox between every subagent;
HOLD/RESCOPE = interrupt) so PM directives are never missed. Gitea/git
merge mechanism; C<->D attachment-path coordination baked in.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5JvzEse4xUxLZKhofyeCD
org_audit.rs (B8 verified-signer test) + the two uncommitted org.rs diffs
(item-CRUD B9-B13, status/audit B8) from the wf_22020aea first-run worktrees.
All superseded by v0.8.0 main; also committed on the -r2 branches. Kept so
nothing is lost when the stale worktrees are removed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5JvzEse4xUxLZKhofyeCD
Dev-A shared item_build foundation + personal --*-stdin; Dev-B org
Card/Key/Totp; Dev-C org Document + attachment storage; Dev-D server
hook grant-scoping. TDD tasks with full code; A gates B/C, D independent.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5JvzEse4xUxLZKhofyeCD
Card/Key/Totp = CLI-only parity via shared item-build module; Document
adds org attachment storage + a relicario-server hook change that
grant-scopes attachment paths (closing the Unrestricted gap). Secrets
via interactive prompts + --*-stdin escape hatches. Four suggested dev
streams (A foundation, B Card/Key/Totp, C Document+attachments, D hook).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L5JvzEse4xUxLZKhofyeCD
Cover two authz gaps left by the B9-B14 org item-CRUD work:
1. Grant-DENIAL on the read/mutate-by-query commands. A second member
added with their own device key but NOT granted `prod` is rejected by
every one of `org get`, `edit`, `rm`, `restore`, and `purge`, and
`org get` (with and without --show) leaks no plaintext. Previously
only `org add` had a denial test. Also asserts the item is untouched
afterward (owner still reads the original password/username).
2. SecureNote body masking: `org get <note>` prints `********` and not
the body; `org get <note> --show` reveals it. Mirrors the existing
Login-password masking assertions in org_items.rs.
New tests/org_authz.rs reuses the multi-member `Dev` harness pattern
from org_lifecycle.rs (one XDG config home + ed25519 device key per
member), so a second member joins with their own keypair.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXpTHcQzw1n8qjYwZqruzQ
Pre-stages the A5 living-docs sweep for the already-merged A (relicario-core org
module) + C (relicario-server pre-receive hook) + CLI admin/rotate/status-audit
work, so the final A5 sweep (after Dev-B B9-B14 merges) is fast.
Adds org sections to docs/FORMATS.md (org repo wire formats + wrapped-key blob
layout), docs/CRYPTO.md (ECIES X25519 wrap/unwrap, no-Argon2id contrast, rotate
re-encryption), docs/SECURITY.md (signature-verifying hook, owner-only elevation,
audit vocabulary, honest limitations), DESIGN.md (org-master-key secrets row +
server org mode + deps), core/cli ARCHITECTURE.md (org module + org_session), and
an Unreleased CHANGELOG entry.
B item-CRUD (org add/get/list/edit/rm/restore/purge + main.rs wiring) and extension
parity are left as explicit TODO. STATUS/ROADMAP mark-shipped and
extension/ARCHITECTURE are deferred to the full A5 (track not yet landed; Dev-D
deferred). All cited code constants pinned with file:line per living-docs discipline.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
The plan's pre-receive-hook pseudocode judged owner-elevation authority on the
post-change `signer.role` (so a self-promoting Admin reads as Owner in the same
commit and self-authorizes the promotion — the exact escalation the gate exists
to stop). f249395 had fixed only the skip-predicate, leaving this final check
vulnerable. Align the plan's `enforce_owner_only_elevation` to the SHIPPED fix
(relicario-server/src/main.rs, aace6f1): derive `signer_may_manage_owners` from
`signer_parent = parent_role(signer.member_id)` (the signer's PRE-commit role;
None -> reject; genesis allowed) and gate on that, never the post-change role.
The spec was already policy-correct in prose ("a member-role-change granting
owner/admin must be signed by an owner") and did NOT carry the vulnerable
implementation detail; strengthened it with an explicit pre-commit-role note so
the design record pins the property and no one re-derives the vulnerable form.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
- verify_org_signer now rejects on a non-zero git verify-commit exit instead of
relying on the stderr fingerprint regex alone (PM hardening note 1).
- org_hook_signed: add commit_signed_by_non_member_is_rejected (exercises the
signature rejection path) and genesis_bootstrap_with_sole_owner_is_accepted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Read the active device's ed25519 seed/pubkey from
devices/<name>/signing.{key,pub}. Adds ssh-key (0.6) as a CLI dep
(already at 0.6.7 in the workspace lock via relicario-core) and
ed25519-dalek as a dev-dep for the round-trip test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- classify_path now Rejects a collection slug containing '.' (mirrors
OrgCollections::validate, plan L317, and item_path's documented contract,
plan L990). Unreachable today since git normalizes './' away, but keeps the
pre-receive hook self-defensive against path traversal.
- Rename test item_write_nested_slug_takes_leading_segment_only ->
item_write_nested_slug_is_rejected (it asserts Rejected; old name misled).
- Add dotted_slug_is_rejected covering the new guard.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01M5brcDrT35r5GaJySXD5ja
Spot-check of the new H-C1 hook code found the owner-only-elevation gate was
bypassable: it skipped any member ALREADY privileged in the parent, but since
Admin is also "privileged", an Admin→Owner promotion was skipped and accepted —
the exact escalation the gate exists to stop, and a failure of its own paired
test. Gate now skips only UNCHANGED roles (parent role == new role), so every
change into a privileged role (Member→Admin/Owner, Admin→Owner, new privileged
member) requires an owner signer.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>