mirror of
https://github.com/AgentSeal/codeburn.git
synced 2026-08-30 10:52:56 +00:00
fix(sync): fresh-machine schedule install, work-matching disclosure line, named drift causes
This commit is contained in:
parent
a9c5d8b6ec
commit
9616bbd0f5
4 changed files with 114 additions and 17 deletions
|
|
@ -29,6 +29,7 @@ import {
|
|||
computeAcceptanceFingerprint,
|
||||
buildDisclosure,
|
||||
CORE_SYNC_FIELD_MEANINGS,
|
||||
detectFingerprintChanges,
|
||||
type FingerprintInput,
|
||||
type DisclosureInput,
|
||||
type Receipt,
|
||||
|
|
@ -622,15 +623,23 @@ export function registerSyncCommands(program: Command): void {
|
|||
cadence,
|
||||
disclosure,
|
||||
attribution: workMatching,
|
||||
input: fingerprintInput,
|
||||
},
|
||||
killed: false,
|
||||
}
|
||||
delete config.auto.killed
|
||||
writeSyncConfig(config)
|
||||
|
||||
await installSchedule(cadence, process.argv[1])
|
||||
|
||||
process.stdout.write(`Automatic sync enabled (${cadence}). Fingerprint: ${fingerprint}\n`)
|
||||
try {
|
||||
await installSchedule(cadence, process.argv[1])
|
||||
process.stdout.write(`Automatic sync enabled (${cadence}). Fingerprint: ${fingerprint}\n`)
|
||||
} catch (schedErr) {
|
||||
process.stderr.write(`Warning: ${(schedErr as Error).message}\n`)
|
||||
process.stderr.write(`Acceptance was stored and will take effect, but the schedule could not be installed.\n`)
|
||||
process.stderr.write(`Run: codeburn sync auto enable --cadence ${cadence} --attribution${opts.attribution ? '' : ''}\n`)
|
||||
process.stderr.write(`Or install manually: launchctl load ~/Library/LaunchAgents/com.codeburn.sync-auto.plist\n`)
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (err) {
|
||||
process.stderr.write(`${(err as Error).message}\n`)
|
||||
process.exit(1)
|
||||
|
|
@ -694,12 +703,7 @@ export function registerSyncCommands(program: Command): void {
|
|||
if (currentFingerprint === accepted.fingerprint) {
|
||||
process.stdout.write('Current fingerprint: MATCHES\n')
|
||||
} else {
|
||||
// Detect what changed
|
||||
const changed: string[] = []
|
||||
const baseCoreKeys = Array.from(CORE_SYNC_ATTRIBUTE_KEYS).sort()
|
||||
if (JSON.stringify(allKeys) !== JSON.stringify(baseCoreKeys)) {
|
||||
changed.push('field set')
|
||||
}
|
||||
const changed = detectFingerprintChanges(accepted.input, fingerprintInput)
|
||||
process.stdout.write(`Current fingerprint: DIFFERS (${changed.join(', ')})\n`)
|
||||
}
|
||||
} catch {
|
||||
|
|
@ -749,16 +753,11 @@ export function registerSyncCommands(program: Command): void {
|
|||
const currentFingerprint = computeAcceptanceFingerprint(fingerprintInput)
|
||||
|
||||
if (currentFingerprint !== accepted.fingerprint) {
|
||||
// Detect what changed
|
||||
const changed: string[] = []
|
||||
const baseCoreKeys = Array.from(CORE_SYNC_ATTRIBUTE_KEYS).sort()
|
||||
if (JSON.stringify(allKeys) !== JSON.stringify(baseCoreKeys)) {
|
||||
changed.push('field set')
|
||||
}
|
||||
const changed = detectFingerprintChanges(accepted.input, fingerprintInput)
|
||||
appendReceipt(buildReceipt(
|
||||
at,
|
||||
currentFingerprint,
|
||||
{ result: 'acceptance-required', changed: changed.length > 0 ? changed : ['unknown'] }
|
||||
{ result: 'acceptance-required', changed }
|
||||
))
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,9 @@ export function buildDisclosure(input: DisclosureInput): string {
|
|||
: input.scopeSinceDays === 0
|
||||
? 'today only'
|
||||
: `last ${input.scopeSinceDays} days`
|
||||
const workMatchingText = input.workMatching
|
||||
? 'on - session-to-commit links are sent'
|
||||
: 'off'
|
||||
|
||||
const fieldsList = input.outboundFields.length > 0
|
||||
? input.outboundFields
|
||||
|
|
@ -89,6 +92,7 @@ export function buildDisclosure(input: DisclosureInput): string {
|
|||
`URL: ${input.destinationUrl}`,
|
||||
`Cadence: ${cadenceText}`,
|
||||
`Scope: ${scopeText}`,
|
||||
`Work matching: ${workMatchingText}`,
|
||||
'',
|
||||
'Data sent to the endpoint:',
|
||||
fieldsList,
|
||||
|
|
@ -109,6 +113,7 @@ export interface AcceptanceRecord {
|
|||
cadence: 'daily' | 'hourly'
|
||||
disclosure: string
|
||||
attribution: boolean
|
||||
input?: FingerprintInput
|
||||
}
|
||||
|
||||
export interface AutoSyncConfig {
|
||||
|
|
@ -141,3 +146,39 @@ export function buildReceipt(at: string, fingerprint: string | undefined, data:
|
|||
...data,
|
||||
}
|
||||
}
|
||||
|
||||
export function detectFingerprintChanges(stored: FingerprintInput | undefined, current: FingerprintInput): string[] {
|
||||
if (!stored) return ['unknown']
|
||||
|
||||
const changes: string[] = []
|
||||
|
||||
if (stored.destination !== current.destination) {
|
||||
changes.push('destination')
|
||||
}
|
||||
|
||||
if (stored.cadence !== current.cadence) {
|
||||
changes.push('cadence')
|
||||
}
|
||||
|
||||
if (stored.scopeSinceDays !== current.scopeSinceDays) {
|
||||
changes.push('scope')
|
||||
}
|
||||
|
||||
if (stored.workMatching !== current.workMatching) {
|
||||
changes.push('work matching')
|
||||
}
|
||||
|
||||
const storedFields = new Set(stored.outboundFields)
|
||||
const currentFields = new Set(current.outboundFields)
|
||||
const added = Array.from(currentFields).filter(f => !storedFields.has(f)).sort()
|
||||
const removed = Array.from(storedFields).filter(f => !currentFields.has(f)).sort()
|
||||
|
||||
if (added.length > 0 || removed.length > 0) {
|
||||
const parts: string[] = []
|
||||
if (added.length > 0) parts.push(`added: ${added.join(', ')}`)
|
||||
if (removed.length > 0) parts.push(`removed: ${removed.join(', ')}`)
|
||||
changes.push(`field set (${parts.join(' / ')})`)
|
||||
}
|
||||
|
||||
return changes.length > 0 ? changes : ['unknown']
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
import { platform } from 'os'
|
||||
import { homedir } from 'os'
|
||||
import { join } from 'path'
|
||||
import { writeFileSync, unlinkSync, existsSync } from 'fs'
|
||||
import { writeFileSync, unlinkSync, existsSync, mkdirSync } from 'fs'
|
||||
import { spawn } from 'child_process'
|
||||
|
||||
const SCHEDULE_AGENT_NAME = 'com.codeburn.sync-auto'
|
||||
|
|
@ -67,8 +67,12 @@ export async function installSchedule(
|
|||
|
||||
const plistContent = buildLaunchAgentPlist(cadence, codeburnPath)
|
||||
const plistPath = launchAgentPath()
|
||||
const agentDir = launchAgentDir()
|
||||
|
||||
try {
|
||||
// Ensure LaunchAgents directory exists
|
||||
mkdirSync(agentDir, { recursive: true })
|
||||
|
||||
// Write the plist
|
||||
writeFileSync(plistPath, plistContent, { mode: 0o644 })
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
computeAcceptanceFingerprint,
|
||||
buildDisclosure,
|
||||
CORE_SYNC_FIELD_MEANINGS,
|
||||
detectFingerprintChanges,
|
||||
type FingerprintInput,
|
||||
type DisclosureInput,
|
||||
} from '../src/sync/consent.js'
|
||||
|
|
@ -239,6 +240,58 @@ describe('Disclosure', () => {
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
it('includes work-matching line when enabled', () => {
|
||||
const input: DisclosureInput = {
|
||||
destination: 'test-org',
|
||||
destinationUrl: 'https://endpoint.example.com',
|
||||
cadence: 'daily',
|
||||
outboundFields: [],
|
||||
workMatching: true,
|
||||
scopeSinceDays: 7,
|
||||
}
|
||||
|
||||
const disclosure = buildDisclosure(input)
|
||||
expect(disclosure).toContain('Work matching: on - session-to-commit links are sent')
|
||||
})
|
||||
|
||||
it('includes work-matching line when disabled', () => {
|
||||
const input: DisclosureInput = {
|
||||
destination: 'test-org',
|
||||
destinationUrl: 'https://endpoint.example.com',
|
||||
cadence: 'daily',
|
||||
outboundFields: [],
|
||||
workMatching: false,
|
||||
scopeSinceDays: 7,
|
||||
}
|
||||
|
||||
const disclosure = buildDisclosure(input)
|
||||
expect(disclosure).toContain('Work matching: off')
|
||||
})
|
||||
|
||||
it('detects removed field set changes', () => {
|
||||
const storedInput: FingerprintInput = {
|
||||
org: 'test-org',
|
||||
destination: 'https://endpoint.example.com',
|
||||
outboundFields: ['ai.cost_usd', 'ai.model', 'plugin.custom'],
|
||||
workMatching: false,
|
||||
scopeSinceDays: 7,
|
||||
cadence: 'daily',
|
||||
}
|
||||
|
||||
const currentInput: FingerprintInput = {
|
||||
org: 'test-org',
|
||||
destination: 'https://endpoint.example.com',
|
||||
outboundFields: ['ai.cost_usd', 'ai.model'],
|
||||
workMatching: false,
|
||||
scopeSinceDays: 7,
|
||||
cadence: 'daily',
|
||||
}
|
||||
|
||||
const changes = detectFingerprintChanges(storedInput, currentInput)
|
||||
expect(changes[0]).toContain('field set')
|
||||
expect(changes[0]).toContain('removed: plugin.custom')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Config with auto block', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue