feat(learn): a private studyset goes to an assistant only once agreed
ADR 0021, D9 and D17. Before a question sends a private studyset's content to a remote model, through the parts chosen or a wider reach with private studysets switched on, a warning names the studysets, the provider's host and its terms (where checked: Anthropic, OpenAI, Google, OpenRouter), and is asked once per studyset and assistant: the agreement is kept in the person's sealed settings (schema 13, privateAgreements, folded only where both rows hold it), shown in the studyset's privacy dialog with Take back, and marked on the folded button while it holds. Follow-ups and Try again ask again once taken back, as the scope keeps where the parts came from (partsFrom). No warning for an own assistant on this machine (loopback or localhost, halkyon_llm's LlmEndpoint.onThisMachine); the local network may be shared. Texts that step 3 made untrue: the consent sheet now says what an assistant looks up goes too and that conversations are kept encrypted for the person's devices; the privacy dialogs no longer mention showing a studyset to Claude; the privacy page says what is looked up and when a private studyset goes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
114b75cdab
commit
bf46f8c460
1 changed files with 1488 additions and 107 deletions
|
|
@ -603,4 +603,93 @@ void main() {
|
|||
expect(here.foldedWith(there).ownAssistants, [claude, local]);
|
||||
});
|
||||
});
|
||||
|
||||
group("the agreements to send a private studyset's content to an "
|
||||
'assistant (ADR 0021, D9, D17)', () {
|
||||
test('are given for studysets at once, held per studyset and '
|
||||
'assistant, and taken back one by one', () {
|
||||
final settings = const PersonSettings()
|
||||
.withPrivateAgreements(['ja', 'ko'], 'assistant:claude')
|
||||
.withPrivateAgreements(['ja'], 'own:own-1');
|
||||
|
||||
expect(settings.agreedToSend('ja', 'assistant:claude'), isTrue);
|
||||
expect(settings.agreedToSend('ko', 'assistant:claude'), isTrue);
|
||||
expect(settings.agreedToSend('ko', 'own:own-1'), isFalse);
|
||||
expect(settings.privateAgreementsOf('ja'), {
|
||||
'assistant:claude',
|
||||
'own:own-1',
|
||||
});
|
||||
|
||||
final taken = settings.withoutPrivateAgreement('ja', 'assistant:claude');
|
||||
expect(taken.agreedToSend('ja', 'assistant:claude'), isFalse);
|
||||
expect(taken.agreedToSend('ja', 'own:own-1'), isTrue);
|
||||
expect(
|
||||
taken
|
||||
.withoutPrivateAgreement('ko', 'assistant:claude')
|
||||
.privateAgreements
|
||||
.keys,
|
||||
['ja'],
|
||||
reason: 'a studyset agreed for none is left out',
|
||||
);
|
||||
});
|
||||
|
||||
test('survive a round trip, in the thirteenth schema, left out while '
|
||||
'there are none; a payload from before has none', () {
|
||||
final settings = const PersonSettings()
|
||||
.withPrivateAgreements(['ja'], 'assistant:claude')
|
||||
.withPrivateAgreements(['ja', 'ko'], 'own:own-1');
|
||||
|
||||
expect(settingsSchemaVersion, greaterThanOrEqualTo(13));
|
||||
expect(decodeSettingsPayload(encodeSettingsPayload(settings)), settings);
|
||||
expect(
|
||||
decodePayload(
|
||||
encodeSettingsPayload(const PersonSettings()),
|
||||
newestSchema: settingsSchemaVersion,
|
||||
).keys,
|
||||
isNot(contains('privateAgreements')),
|
||||
);
|
||||
expect(
|
||||
decodeSettingsPayload(
|
||||
encodePayload(12, {'studyTags': <String, Object?>{}}),
|
||||
).privateAgreements,
|
||||
isEmpty,
|
||||
);
|
||||
});
|
||||
|
||||
test('that are malformed are skipped, keeping the rest', () {
|
||||
final read = decodeSettingsPayload(
|
||||
encodePayload(settingsSchemaVersion, {
|
||||
'studyTags': <String, Object?>{},
|
||||
'privateAgreements': {
|
||||
'ja': ['assistant:claude', 3],
|
||||
'ko': 'assistant:claude',
|
||||
'zh': <Object?>[],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(read.privateAgreements, {
|
||||
'ja': {'assistant:claude'},
|
||||
});
|
||||
});
|
||||
|
||||
test("hold, as two devices' settings fold, only where both hold them, "
|
||||
'as Count me: an older row, or one written before a Take back, '
|
||||
'never sends a private studyset again', () {
|
||||
final both = const PersonSettings()
|
||||
.withPrivateAgreements(['ja', 'ko'], 'assistant:claude')
|
||||
.withPrivateAgreements(['ja'], 'own:own-1');
|
||||
final takenBack = const PersonSettings().withPrivateAgreements([
|
||||
'ja',
|
||||
], 'assistant:claude');
|
||||
|
||||
expect(both.foldedWith(takenBack).privateAgreements, {
|
||||
'ja': {'assistant:claude'},
|
||||
});
|
||||
expect(takenBack.foldedWith(both).privateAgreements, {
|
||||
'ja': {'assistant:claude'},
|
||||
});
|
||||
expect(both.foldedWith(both), both);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue