Skip to content

Session-consent gate: the deferred half of SC-05 (founder legal review, persistence, UI) #163

Description

@myselfsiddharth

Follow-up from #102 (landing in #162). SC-05's gate landed — src/session/consent.ts: SessionAuthorization.authorize(baseUrl, consent?) refuses a non-local session establishment with no ConsentAcknowledgment, establishSession requires one, and the refusal path is tested (tests/unit/session-consent.test.ts). ADR-0018 decided the shape — a stored consent record, checked before every session-establishing run. What it deliberately deferred, with reasons, is the operational half. This issue is where that comes back.

Deferral is recorded in docs/privacy/session-custody.md's SC-05 section and in docs/privacy/session-consent-copy.md's own "Open questions"; those are the starting point, not the answer.

What is deferred, and what it costs to keep deferring

Deferred Current state Prerequisite it blocks
Legal review of the copy session-consent-copy.md is a first draft (confidence: LOW), reviewed by nobody Showing the copy to any real user. Issue #102 required review "by whoever owns legal risk here (founder)" before that; it has not happened
Consent-record persistence ConsentAcknowledgment.record() produces an in-memory object only. Nothing reads or writes one from disk, a keychain, or anywhere durable The gate ever authorizing a real non-local run. Today SessionAuthorization.authorize can only refuse a non-local target — no caller anywhere can supply a consent argument that came from a real, persisted acknowledgment, because nothing produces one
The UI/CLI moment that displays the copy Nothing shows the text to anyone. No banner, no prompt, no click-through Same as above — persistence needs something to write the record after a human agrees, and nothing does that yet

The sharpest limitation, stated plainly rather than left in a module docstring

ConsentAcknowledgment.record() takes no required arguments. Call it with nothing and it happily returns a valid, well-typed acknowledgment. That means what the gate proves today is "a caller asserted consent", not "a human was shown the copy and agreed to it." The private-constructor/#brand pattern stops an object literal from forging one, but nothing stops a caller from calling record() without ever having displayed anything to anyone — there is no proof-of-display baked in. This is fine right now because there is no real caller at all (see below), but it is exactly the gap that must close before this gate means what SC-05 needs it to mean, and it closes only when persistence and UI land together — persistence gives the record somewhere durable to live tied to an actual display event, and UI is the thing that only calls record() after showing the copy and capturing an affirmative response.

Why it is safe to sit on for now, and what ends that

Nothing in this repo establishes a session against a non-local target yet — re-verified: SessionAuthorization.authorize has exactly three callers (src/recorder/cli.ts, experiments/gate-v1/live-run.ts, and the test suite), and all three target the local test-bed with fixture credentials the project owns. Exposure today is genuinely zero, which is what makes the deferral honest rather than convenient — the same argument #146 made for SC-01's custody half.

That ends the moment PRD phase 1's real Fork A caller — something establishing a session against an account this project does not own — lands. This issue should be resolved before that happens, not after, for the same reason #98/#146 built SC-01's per-tenant derivation and custody shape ahead of a real caller: retrofitting consent after a real non-local run has already happened is not a retrofit that can undo the run.

What to decide

  1. Founder legal review of the draft copy. Signoff, rewrite, or a different bar entirely — docs/privacy/session-consent-copy.md is the artifact to review, and its own "Open questions" section names what it could not verify (legal adequacy chief among them).
  2. Where the consent record persists, and how it's keyed. A local config file, an OS keychain entry, something else. Also open (from ADR-0018): whether one acknowledgment should cover every future non-local target, or whether a distinct one is needed per target — agreeing to automate one account says nothing about another.
  3. What UI shows the copy and captures the confirmation. A CLI prompt is the natural fit for this repo's current shape (it is a CLI/library today, not a hosted product — see ADR-0018's rejection of an onboarding flow), but this is not decided here.
  4. Whether ConsentAcknowledgment needs some proof-of-display, so that the object itself is evidence a human saw the specific copy version being acknowledged, not just a caller's say-so. Could be as simple as requiring the exact copy text (or its hash) as an argument to record(), checked against the current CONSENT_COPY_VERSION's known content, rather than accepting a bare version string.

Constraints

  • No credentials, cookies, or session material in the consent record, ever (CONTRIBUTING rule 1) — ConsentAcknowledgment today carries copy_version + acknowledged_at only, and whatever persistence lands must not widen that.
  • Whatever lands keeps the compile-time guarantee ADR-0018 built: SessionAuthorization stays the only door into establishSession, and a real UI/persistence caller should satisfy the same SessionAuthorization.authorize contract rather than growing a second path.
  • Local dev and CI need nothing from this — the same posture Session-key custody: the deferred half of SC-01 (KMS, rotation, tenant offboarding) #146 (Decision 4) took for SC-01's custody: no test or fixture path should start depending on a real persistence/UI mechanism that doesn't exist for local targets, which need no consent at all.

Metadata

Metadata

Labels

founderFounder decision or ownershipsecuritySecurity-related

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions