mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-11-05 18:07:09 +00:00
Align privacy guide checks with provider and telemetry boundaries
Keep the reviewed BYOK disclosures while testing their meaning across Markdown wrapping and UK spelling. Limit retired-offer checks to Pulse-hosted models, and retain explicit checks for direct provider traffic, restricted identifiers and local endpoint verification. Distinguish model context from pseudonymous usage telemetry without an ambiguous anonymity phrase. Contract-Neutral: Existing help and its regression checks only; model transport, redaction, authorisation, retention and telemetry runtime are unchanged. Change-source: pulse-maintainer
This commit is contained in:
+1
-1
@@ -879,7 +879,7 @@ happens at the configured endpoint. With a hosted model, context travels directl
|
||||
from a self-managed Pulse instance to that provider, not through Pulse
|
||||
infrastructure.
|
||||
|
||||
- **Model context is not anonymous telemetry.** It can include resource names
|
||||
- **Model context differs from usage telemetry.** It can include resource names
|
||||
and addresses, monitoring state, relationships, operator notes and objectives,
|
||||
prior findings and tool results used in the run. Review the chosen provider's
|
||||
retention and training policies before enabling scheduled Patrol or an
|
||||
|
||||
@@ -879,7 +879,7 @@ happens at the configured endpoint. With a hosted model, context travels directl
|
||||
from a self-managed Pulse instance to that provider, not through Pulse
|
||||
infrastructure.
|
||||
|
||||
- **Model context is not anonymous telemetry.** It can include resource names
|
||||
- **Model context differs from usage telemetry.** It can include resource names
|
||||
and addresses, monitoring state, relationships, operator notes and objectives,
|
||||
prior findings and tool results used in the run. Review the chosen provider's
|
||||
retention and training policies before enabling scheduled Patrol or an
|
||||
|
||||
@@ -110,6 +110,7 @@ describe('systemSettings store', () => {
|
||||
path.join(frontendRoot, 'public', 'docs', 'PRIVACY.md'),
|
||||
'utf8',
|
||||
);
|
||||
const privacyText = privacyDoc.replace(/\s+/g, ' ');
|
||||
|
||||
expect(privacyDoc).not.toContain('secure remote web access');
|
||||
expect(privacyDoc).toContain('Relay does not provide remote access to the web UI');
|
||||
@@ -124,8 +125,8 @@ describe('systemSettings store', () => {
|
||||
expect(privacyDoc).toContain('uses request IP addresses transiently for abuse/rate limiting');
|
||||
expect(privacyDoc).toContain('Reset ID');
|
||||
expect(privacyDoc).toContain('active Patrol objective briefs');
|
||||
expect(privacyDoc).toContain('encrypted at rest in the local organization data directory');
|
||||
expect(privacyDoc).toContain('objective text is not included in Pulse usage telemetry');
|
||||
expect(privacyText).toMatch(/encrypted at rest in the local organi[sz]ation data directory/);
|
||||
expect(privacyText).toContain('objective text is not included in Pulse usage telemetry');
|
||||
expect(privacyDoc).toContain('Pulse Intelligence Patrol control completed operations loop 30d');
|
||||
expect(privacyDoc).toContain(
|
||||
'Pulse Intelligence Patrol control paid resolved operations loop 30d',
|
||||
@@ -169,13 +170,19 @@ describe('systemSettings store', () => {
|
||||
|
||||
it('documents self-hosted AI provider transport and resource-policy redaction in the privacy doc', () => {
|
||||
const privacyDoc = readFileSync(path.join(repoRoot, 'docs', 'PRIVACY.md'), 'utf8');
|
||||
const privacyText = privacyDoc.replace(/\s+/g, ' ');
|
||||
|
||||
expect(privacyDoc).toContain(
|
||||
'AI prompts from self-managed installs do not transit Pulse infrastructure',
|
||||
expect(privacyText).toContain(
|
||||
'providers receive this context directly from your self-managed Pulse instance',
|
||||
);
|
||||
expect(privacyDoc).toContain(
|
||||
'governed resource details use the same resource-policy redaction',
|
||||
expect(privacyText).toContain('AI prompts do not transit Pulse infrastructure');
|
||||
expect(privacyText).toContain('Pulse applies secret filtering and resource-policy handling');
|
||||
expect(privacyText).toContain('known restricted resource identifiers are redacted');
|
||||
expect(privacyText).toContain(
|
||||
'Internal and Sensitive resources can still be shared with a hosted model',
|
||||
);
|
||||
expect(privacyText).toContain(
|
||||
'verify that the chosen endpoint runs within your trusted network and does not forward requests elsewhere',
|
||||
);
|
||||
expect(privacyDoc).toContain('Local providers stay on your network');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,17 @@ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../../../'
|
||||
const readRepoFile = (relativePath: string) =>
|
||||
readFileSync(resolve(repoRoot, relativePath), 'utf-8');
|
||||
|
||||
// Provider privacy disclosures must be able to describe remote models. This
|
||||
// guard concerns the retired Pulse-hosted offering, not an operator's provider.
|
||||
const pulseHostedOffering = /Pulse[- ]hosted (?:AI|models?)/i;
|
||||
|
||||
describe('quickstart copy contract', () => {
|
||||
it('distinguishes provider privacy disclosures from a Pulse-hosted offering', () => {
|
||||
expect('a hosted model receives provider-bound context').not.toMatch(pulseHostedOffering);
|
||||
expect('Pulse-hosted AI with no API key').toMatch(pulseHostedOffering);
|
||||
expect('Pulse-hosted model').toMatch(pulseHostedOffering);
|
||||
});
|
||||
|
||||
it('keeps self-hosted public docs free of hosted quickstart claims', () => {
|
||||
const readme = readRepoFile('README.md');
|
||||
const pulsePro = readRepoFile('docs/PULSE_PRO.md');
|
||||
@@ -34,8 +44,7 @@ describe('quickstart copy contract', () => {
|
||||
]) {
|
||||
expect(copy).not.toMatch(/quickstart/i);
|
||||
expect(copy).not.toContain('quickstart:pulse-hosted');
|
||||
expect(copy).not.toMatch(/hosted AI/i);
|
||||
expect(copy).not.toMatch(/hosted model/i);
|
||||
expect(copy).not.toMatch(pulseHostedOffering);
|
||||
expect(copy).not.toMatch(/hosted[\s\S]{0,120}no API key/i);
|
||||
expect(copy).not.toMatch(/Pulse-hosted[\s\S]{0,120}no API key/i);
|
||||
expect(copy).not.toMatch(/Pulse Account[\s\S]{0,120}no API key/i);
|
||||
|
||||
@@ -802,7 +802,8 @@ func TestTelemetryPrivacyDocsKeepObserverProposalContentLocal(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("read %s: %v", relativePath, err)
|
||||
}
|
||||
content := string(raw)
|
||||
// Markdown wrapping must not change the required privacy disclosures.
|
||||
content := strings.Join(strings.Fields(string(raw)), " ")
|
||||
for _, required := range []string{
|
||||
"model-authored observer proposal",
|
||||
"encrypts that artifact with the retained objective",
|
||||
|
||||
Reference in New Issue
Block a user