A-vs-E keep-both integration in 3 security-sensitive SW files:
- shared/messages.ts: kept E's attach_vault keyfile second-factor fields
(secondFactor/referenceImageBytes?/keyfileBytes?) AND my 4 org message types.
- vault.ts: registerDeviceAndPersistConfig auto-merged to take BOTH E's
factorStorage (image|keyfile) AND my handle (Task 4.5 device-key persist);
both create/attach call sites pass (factorStorage, deviceName, h) so each
flow does E secret-gen AND my register+persist in one coherent path.
- router/popup-only.ts: took E's 'unlock' -> handleUnlock refactor and moved my
device-key restore + migration into handleUnlock's shared tail (after
setCurrent, before manifest fetch) so it runs for both image + keyfile unlock.
- keyfile-unlock.test.ts: makeWasm mock gains persist_device_key + restore_device_key
stubs (the merged create/attach/unlock flow now calls them) — no assertion weakened.
Gates: SW vitest 175/175 (18 files); cargo core+wasm 243/0; wasm32 build clean;
clippy -D warnings clean; build:all clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
Wires 4 SW messages to the org-vault.ts read functions (Task 5b):
- org_switch: opens org vault via openOrg(), caches OrgHandleState, sets
session context; switching to 'personal' resets context without openOrg.
Offline-aware: if openOrg throws a network error and a prior cached state
exists, reuses it read-only (offline:true) via exported isNetworkError.
- org_list_items: returns cached, grant-filtered OrgManifestEntry[] (not
personal ManifestEntry[]) via listOrgItems(requireCurrentOrgState()).
- org_get_item: decrypts a single org item via getOrgItem.
- org_list_collections: returns granted Collection[] via listOrgCollections.
Three-place rule satisfied for all 4 messages:
1. messages.ts PopupMessage union (4 variants)
2. messages.ts POPUP_ONLY_TYPES (4 entries)
3. popup-only.ts switch (4 case arms)
Cache lifecycle / use-after-free guard:
- clearOrgCache() added to org-handlers.ts; exports.
- Called in popup-only.ts 'lock' handler (before session.clearCurrent).
- Called in index.ts inactivity-timer onExpired (before clearCurrent).
Plan doc fixes:
- Line ~410: org_list_items return type corrected to OrgManifestEntry[].
- Line ~47: org_unwrap_key signature updated (no device_private_key_base64
param — reads DEVICE_STATE internally).
9 new tests in org-handlers.test.ts (TDD: all watched fail before passing).
Full SW suite: 167 tests (17 files) green. build:all type-checks clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
Additive conflict in extension/src/wasm.d.ts resolved by keeping BOTH the
org_* declarations (org_unwrap_key/org_manifest_decrypt/org_manifest_encrypt)
and the keyfile_* declarations (unlock_with_secret/keyfile_encode/keyfile_decode).
Rust lib.rs (core + wasm) auto-merged cleanly with both feature sets.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
Universal isomorphic-git receive-pack push path shipped & verified: sshsig.ts +
mem-fs.ts + git-push.ts + commitSigned on GitHost/Gitea/GitHub. Full SW suite
163/163, build:all bundles isomorphic-git into the SW, /security-review clean.
Tasks 3-5 remain HELD on Dev-A + Dev-B merge.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKBbrAjmpVXMEK15pNi3Ha
Mini-spike (isomorphic-git) proved the universal write path end-to-end with no
live creds: Tier 1 commit({onSign}) embeds our SSHSIG -> git verify-commit +
verify-org-commit both PASS; Tier 2 push over git-receive-pack to a bare repo
running the real org pre-receive hook ACCEPTS a member-signed commit and
REJECTS a non-member one.
- Spike doc: + Addendum recording the isomorphic-git Tier 1/2 results + Task 2
implications (in-memory fs only, http/web, onAuth Basic, no new WASM export).
- Plan doc: architecture re-scoped (Gitea has no write Git Data API at any
version -> single universal receive-pack path for both hosts); Task 1 marked
DONE/GO; Task 2 rewritten as the universal commitSigned; Dev-A OrgManifest
contract + Tasks 3-5 HELD-on-A/B recorded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKBbrAjmpVXMEK15pNi3Ha
README: open with the two-independent-secrets-into-KDF thesis; reframe the
second factor as pluggable (reference image or key file); keep the stego
deep-dive as a distinctive option; update all scenario tables, recovery section,
env-var hint, and roadmap line to use "second-factor secret" language.
docs/CRYPTO.md: add "Pluggable second-factor containers" subsection explaining
that the 32-byte input to Argon2id is identical regardless of container;
reference-image / key-file / recovery-QR are interchangeable carriers.
Update org-vs-personal comparison table and entropy labels.
docs/FORMATS.md: document the .relkey armor format (relicario-keyfile-v1 +
base64 line) and the params.json second_factor field ("image"|"keyfile",
absent means "image" for back-compat). Cite crates/relicario-core/src/keyfile.rs
keyfile_encode/keyfile_decode with TODO-pin comments. Update KDF input formula
from image_secret to second_factor_secret.
DESIGN.md: split secrets-map "Reference image bytes" row to distinguish image
and key-file containers; add key-file row noting keyfileBase64 in
chrome.storage.local has the same in-the-clear posture as imageBase64.
Update vault-layout note and KDF conventions row.
docs/SECURITY.md: add "Second-factor storage posture" subsection explicitly
stating .relkey and keyfileBase64 are the second factor in the clear — same
posture as the reference JPEG / imageBase64, not weaker, protected by the
passphrase invariant. Add RELICARIO_KEYFILE to the env-var trust surface table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014THSV6cA4Gxa7bxFfisHBB
Task 1 GO/NO-GO for v0.9.0 Plan C (org write). Proven locally end-to-end: a
commit signed with ONLY the raw sign_for_git primitive + manual SSHSIG framing
(no new WASM export) passes both `git verify-commit` and `relicario-server
verify-org-commit`, incl. item+manifest dual-write to a granted collection with
full path authz. Hook matches on the signing-KEY fingerprint -> members[].
ed25519_pubkey; committer/author text is free-form (not checked).
Transport diverges by host:
- GitHub: Git Data API `POST /git/commits` has a `signature` field, inserted
verbatim into gpgsig -> LIKELY-GO (1 live round-trip to confirm SSHSIG, not
just PGP, is stored byte-for-byte).
- Gitea: `/git` API is GET-only on live 1.25.5 (no create-commit) -> REST
NO-GO. Viable path is a `git-receive-pack` smart-HTTP packfile push
(endpoint reachable, 401 Basic-auth) = bigger Task 2 than planned.
Also flags a pre-existing latent bug: gitea.ts putBlob calls nonexistent
Gitea /git write endpoints. Scope decision (GitHub-only vs build the Gitea
packfile pusher vs defer write) raised to PM.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LKBbrAjmpVXMEK15pNi3Ha
Product audit (product-expert skill) recommended two priority items; this
lands the audit record plus the two approved design specs that will drive
the v0.9.0 multi-agent train.
- reviews/2026-06-20-product-audit.md — the roadmap audit (reality check,
recommendations, PM brief) that drove the two items.
- specs/2026-06-20-extension-org-gui-design.md — bring the org vault to the
extension at read+write parity. Org write is gated on a Day-1 signing
spike (the org hook rejects unsigned commits; the extension pushes
unsigned today; sign_for_git exists in WASM but is unused). Spike-fail
degrades to read-only + write follow-up.
- specs/2026-06-20-pluggable-second-factor-design.md — key file as an
alternative second factor (same 32-byte secret, same KDF; crypto-light),
chosen at setup via a non-secret params hint, plus the positioning pivot.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQbgrP6KQW5pibjbPEoTSs
Document the attachments/<slug>/<item-id>/<att-id>.enc layout (exactly 3
segments, slug-authorized by the pre-receive hook, never decrypted
server-side) and DEFAULT_ORG_ATTACHMENT_MAX_BYTES = 10 MiB, citing
org_session.rs:24 and the mirrored personal default settings.rs:116.
Forward-planning survey (not v0.8.1 scope). Two independent sweeps (PM 3-agent +
Dev-D 4-reader workflow with adversarial critic), reconciled and hand-verified
against source at b09e0ce. Headline: core item-CRUD at full parity (extension
often ahead); genuine extension gaps cluster in metadata management (group/tag/
filter editing limited to specific forms, zero favorites UI), backend-exists-no-
wire items (remove_attachment, per-item purge, isInTab attachment gate), autofill
hostname matching, and the org vault (largest, already specced/deferred).
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
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
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>
Path-scoped collection storage (items/<slug>/<id>.enc) for hook-enforceable
writes; signature-verifying pre-receive hook on every commit; audit actor from
verified signer (trailers advisory + TAMPERED flag); org item CRUD in scope;
rotate-key re-encrypts all items; transfer/delete-org; extension parity in
phase 1; living-docs impact section.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- queue.ts: append every posted message to relay-log.jsonl (full body,
survives the consume-once drain + restarts). gitignored.
- server.ts: bump the stdout preview from 60 to 120 chars.
- tools/relay/pm: absolute-path bash wrapper (read|pending|send) so relay
ops work from any cwd without cd or hand-built JSON escaping.
- Fold in Dev-C's Phase 6 ARCHITECTURE.md slice as a coordination artifact.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Completes the extension restructure begun in v0.6.0. Phases 3 (setup
wizard SW migration + step registry), 4 (vault.ts split + vault_locked
lift), and 6 (get_vault_status + sidebar status indicator) all merged to
main (9df2fee, 3b8368d, 397cc78) via three parallel worktree streams.
This commit is the release-prep wrap-up:
- Version bump to v0.7.0 across the three relicario crates + Cargo.lock,
extension/package.json, and both extension manifests (the manifests had
lagged at 0.5.0 — corrected here).
- CHANGELOG.md v0.7.0 entry.
- STATUS.md: extension restructure moved to shipped; Phases 3/4/6 landing
section added.
- ROADMAP.md: v0.7.0 row added; Up-next now command palette.
- extension/ARCHITECTURE.md: all three phases integrated (new vault-*
modules, setup-steps.ts, get_vault_status protocol + status indicator,
vault_locked lift, git-host sync cache).
- Plan completion checkboxes ticked.
Task 7.1 verification: done-criteria sweep all green; 423/423 vitest;
build:all clean (only the pre-existing 4MB WASM size warning).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
24 tasks across 6 phases derived from the 2026-05-04 extension restructure
spec. Per-task bite-sized steps (TDD where new behavior, verify-existing-
tests where pure relocation) with explicit file/line citations and full
code snippets.
Phase 1 (StateHost typing, S-M, blocks 3+4): 5 tasks
Phase 2 (storage.ts + itemToManifestEntry, S): 3 tasks
Phase 3 (setup wizard SW migration + step registry, L): 7 tasks
Phase 4 (vault.ts split into 5 modules + vault_locked lift, M): 7 tasks
Phase 5 (P2 cluster: timer/gitHost/teardown/allSettled/debounce, M): 5 tasks
Phase 6 (get_vault_status + sidebar status indicator, S-M): 3 tasks
Task 7.1 (final verification sweep against spec Done criteria).
Recommended sequence: 1 → 2 → 5 → 4 → 6 → 3 (independents first, then
the typed-StateHost-dependent phases, then Phase 3 last because it's the
biggest single phase and benefits from all the supporting infra in
place). Max subagent parallelism: 3 streams.
Cross-plan: explicit out-of-scope notes for Plan A (security/docs polish,
already shipped) and Plan B (CLI restructure, already shipped). The
wasm.d.ts file is not touched by this plan (verify empty diff at done).
STATUS + ROADMAP updated to point at the plan.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rule #4 codifies the discipline that prevents the kind of drift the
2026-05-30 status-audit found: Phase 2B, v0.5.1 Streams A/B/C, and 1C-γ
all stealth-shipped 2-3 weeks earlier with their plan checkboxes never
ticked and STATUS.md still listing them as "Up next".
Two halves to the rule:
- Ship side: ticking the boxes is part of shipping. A commit that lands
plan work also ticks that plan's boxes (or an immediately-following
docs commit does).
- Execute side: before starting an unchecked plan, spot-check git log
for distinctive symbols/files — re-executing already-merged work is
the worst failure mode of the drift.
Also applies the rule retroactively to the doc-structure redesign plan:
all 37 sub-step checkboxes flipped to [x]. Tasks 1-4 (rename, scope
headers + Next: footers, link fixes, CLAUDE.md table) shipped in
36a59cd..bae3f7c. Task 5's six verification steps all pass (Step 3's
grep matches are false positives — they're correct new-path sibling
links from inside docs/ to docs/, not stale old-path uses).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Rewrites every markdown reference to the old paths:
- ARCHITECTURE.md → DESIGN.md
- docs/ARCHITECTURE.md → docs/CRYPTO.md
- FORMATS.md → docs/FORMATS.md
Touches CLAUDE.md (living-docs table + planning-references list),
per-crate ARCHITECTURE.md cross-refs, and any specs in
docs/superpowers/specs/ that referenced the old paths. Audit
history and test-run logs intentionally left untouched.
Spec: docs/superpowers/specs/2026-05-30-doc-structure-redesign-design.md
Each of the eight tour docs (README, DESIGN, docs/CRYPTO,
docs/FORMATS, docs/SECURITY, crates/relicario-core/ARCHITECTURE,
crates/relicario-cli/ARCHITECTURE, extension/ARCHITECTURE) now
declares its scope in a blockquote under its H1 and ends with a
single-line "Next:" pointer to the next doc in the canonical
reading order: README → DESIGN → CRYPTO → FORMATS → SECURITY →
core → cli → extension.
Also trimmed README's mid-section "Architecture" stub to a one-
paragraph pointer at DESIGN.md (was duplicating cross-codebase
content and referencing a non-existent docs/architecture/ tree).
Renamed docs/CRYPTO.md's H1 from "Relicario — Architecture" to
"Relicario — Crypto Pipeline" to match the file's renamed scope.
Spec: docs/superpowers/specs/2026-05-30-doc-structure-redesign-design.md
Five sequential tasks, one commit each, all mechanical:
1. git mv the three doc files
2. add scope headers + Next: footers to the eight tour docs
(also trim README architecture stub)
3. fix incoming links to old paths
4. update CLAUDE.md table + add 3 discipline rules
5. verification gate
Spec: docs/superpowers/specs/2026-05-30-doc-structure-redesign-design.md