You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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
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).
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.
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.
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.
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 noConsentAcknowledgment,establishSessionrequires 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 indocs/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
session-consent-copy.mdis a first draft (confidence: LOW), reviewed by nobodyConsentAcknowledgment.record()produces an in-memory object only. Nothing reads or writes one from disk, a keychain, or anywhere durableSessionAuthorization.authorizecan only refuse a non-local target — no caller anywhere can supply aconsentargument that came from a real, persisted acknowledgment, because nothing produces oneThe 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/#brandpattern stops an object literal from forging one, but nothing stops a caller from callingrecord()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 callsrecord()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.authorizehas 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
docs/privacy/session-consent-copy.mdis the artifact to review, and its own "Open questions" section names what it could not verify (legal adequacy chief among them).ConsentAcknowledgmentneeds 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 torecord(), checked against the currentCONSENT_COPY_VERSION's known content, rather than accepting a bare version string.Constraints
ConsentAcknowledgmenttoday carriescopy_version+acknowledged_atonly, and whatever persistence lands must not widen that.SessionAuthorizationstays the only door intoestablishSession, and a real UI/persistence caller should satisfy the sameSessionAuthorization.authorizecontract rather than growing a second path.