Skip to content

feat(blueprints): saying why a blueprint is untrusted (T79b) - #71

Merged
haiquang9994 merged 6 commits into
masterfrom
t79b-why-a-blueprint-is-untrusted
Sep 1, 2026
Merged

feat(blueprints): saying why a blueprint is untrusted (T79b)#71
haiquang9994 merged 6 commits into
masterfrom
t79b-why-a-blueprint-is-untrusted

Conversation

@haiquang9994

Copy link
Copy Markdown
Collaborator

Roadmap task T79b, phase 8 — design in
docs/superpowers/specs/2026-09-02-t79b-why-a-blueprint-is-untrusted-design.md.

T78a wrote a boolean, and the daemon knew more than a boolean. A blueprint that arrived with no
signature and one whose signature did not verify both reached a person as one sentence —
untrusted: nothing vouches for it, and nothing will — while only the second is the event the
gallery key exists to catch. The difference survived in the daemon's log and nowhere a client could
read it. Found by T79a's acceptance run and left alone there rather than widening that task into
mixengine-proto.

Trust is still decided once. Nothing here re-checks a signature or moves a trusted column;
this is a reason beside an answer that was settled when the row was written.

What changed

  • mixengine-proto: SignatureCheck (verified / missing / rejected) on BlueprintSummary
    and BlueprintPlan, beside trusted rather than instead of it.
  • mixengine-core: Trust — one value the write side settles, which derives both columns, so the
    answer and the reason cannot be set apart. Migration 0015 adds blueprints.signature.
  • mixengine-daemon: vouched_for answers Trust instead of bool. It is the function that
    already knew all three cases and threw two away.
  • mixengine-cli: three sentences where there was one — at import, in the TRUST column
    (signed / unsigned / mismatched), and in the question asked before a [scaffold] command
    runs.

Three decisions worth reading

The reason had to be a column. blueprint.list reads rows, so a test asserting only what
import returned would stay green with the migration broken; the daemon test reads the listing back
for that reason.

ON CONFLICT DO UPDATE is where a stale reason would have come from. Without
signature = excluded.signature, re-importing an unsigned file over a verified row leaves
trusted = 0 beside signature = 'verified'. Its test was checked by removing that line and
watching it go red.

No fourth variant for "signed by another key." The only thing that could tell it from "signed by
the gallery and then edited" is the key id inside the .minisig, which is not authenticated —
whoever edits the file edits the key id with it. So the sentence is "a signature came with it, and
it is not the gallery's", which is true of all three failures the verifier folds together, including
a colleague's own key and a corrupt .minisig.

The migration backfills only the knowable half: an imported row that is trusted can only have come
from a signature that verified; an untrusted one is either of the other two, and stays NULL rather
than guessed.

Pinning the reason into ScaffoldConsent was considered and declined — command is already pinned
and both untrusted kinds sit behind one flag — with the case that would reopen it written into the
design's D9.

Verified on a machine

Against the sandbox home T79a left behind, which holds rows written before this task, so the
migration ran on real data rather than an empty table:

  • laravel (imported, trusted) backfilled to verifiedsigned; bare and edited (imported,
    untrusted) stayed NULL → untrusted, not a guess.
  • import with nothing beside it → nothing came with it to vouch for it; with a file that is not a
    signature → a signature came with it, and it is not the gallery's; with the gallery's own
    published pair → signed by the gallery key; with one byte appended to those signed bytes →
    untrusted again.
  • the listing shows signed / unsigned / mismatched, and still does after the daemon is
    restarted
    — the half that proves the column.

Not covered by a test, and why

The Verified arm is unreachable from the test suite: the daemon checks against the compiled-in
gallery key and its secret half lives in the packaging repository's Actions secrets. It is proved
the way T79a proved it, with a published pair from the blueprints release tag. The [scaffold]
question is only asked on a real apply, so its sentence was extracted into a pure function and
tested there.

@haiquang9994
haiquang9994 merged commit 42e18bc into master Sep 1, 2026
10 checks passed
@haiquang9994
haiquang9994 deleted the t79b-why-a-blueprint-is-untrusted branch September 1, 2026 21:46
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