@muthuishere/vsync 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/repoconfig.ts CHANGED
@@ -33,8 +33,15 @@ export type ConfigFile = {
33
33
  gh?: { repo: string };
34
34
  gcp?: { project: string };
35
35
  };
36
+ // Optional audit-log block. Absent on disk → defaults to `{ enabled: true }`
37
+ // (audit is on by default — see SPEC-v0.4 §9). Explicit `{ enabled: false }`
38
+ // round-trips through save/load unchanged.
39
+ audit?: { enabled: boolean };
36
40
  };
37
41
 
42
+ /** Per-(repo, env) audit preference, defaulted. Source of truth for callers. */
43
+ export const DEFAULT_AUDIT_ENABLED = true;
44
+
38
45
  /** Full path for a given (repo, env). env is lowercased; repo is taken as-is. */
39
46
  export function configFilePath(repo: string, env: string): string {
40
47
  if (!repo) throw new Error("repo is required");
@@ -95,6 +102,10 @@ export async function loadConfigFile(
95
102
  const json = gunzipSync(buf).toString("utf8");
96
103
  const parsed = JSON.parse(json);
97
104
  validateConfigFile(parsed);
105
+ // Default-on for `audit` — absent block means "enabled" per spec §9.
106
+ if (parsed.audit === undefined) {
107
+ parsed.audit = { enabled: DEFAULT_AUDIT_ENABLED };
108
+ }
98
109
  return parsed;
99
110
  }
100
111
 
@@ -160,4 +171,12 @@ export function validateConfigFile(cfg: unknown): asserts cfg is ConfigFile {
160
171
  throw new Error("config: sync.gcp.project must be a string if sync.gcp is present");
161
172
  }
162
173
  }
174
+ if (c.audit !== undefined) {
175
+ if (typeof c.audit !== "object" || c.audit === null) {
176
+ throw new Error("config: audit must be an object if present");
177
+ }
178
+ if (typeof c.audit.enabled !== "boolean") {
179
+ throw new Error("config: audit.enabled must be a boolean");
180
+ }
181
+ }
163
182
  }
@@ -73,6 +73,33 @@ plus the salt from the per-repo config. To decrypt by hand:
73
73
 
74
74
  Easier: just \`vsync pull\` again to restore from S3.
75
75
 
76
+ ## Audit log
77
+
78
+ Every \`pull\`, \`push\`, \`import\`, and \`export\` appends a CSV row to
79
+ \`s3://<bucket>/<repo>/<env>/audit.csv\` capturing timestamp, action,
80
+ hostname, local IP, OS user, git email, and a free-form \`meta\` JSON
81
+ cell. Best-effort: an audit-write failure prints a warning and never
82
+ fails the parent command.
83
+
84
+ On by default. Three ways to opt out:
85
+
86
+ - \`--no-audit\` on a single invocation
87
+ - \`cfg.audit.enabled: false\` in the per-repo config
88
+ - \`vsync init <env> --audit=off\` at setup (or pick "off" at the first-time prompt)
89
+
90
+ Tag a row with context via four merging input paths, in increasing
91
+ priority order: \`$VSYNC_AUDIT_META\` (JSON) < \`$VSYNC_AUDIT_NOTE\` <
92
+ \`--meta key=value\` (repeatable) < \`--note=<text>\`. Later sources
93
+ overwrite same-named keys from earlier ones.
94
+
95
+ View the log with \`vsync audit <env>\` (\`--limit=N\`, \`--all\`, \`--csv\`).
96
+ Pretty table by default, newest first; \`--csv\` is raw passthrough.
97
+
98
+ Honesty clause: transparency for honest users, not tamper-proof
99
+ (anyone with bucket write can rewrite the file). It can't recover
100
+ already-pulled secrets either — once a teammate has pulled, they hold
101
+ a local copy.
102
+
76
103
  ## Rules for AI agents working in this repo
77
104
 
78
105
  1. **Never commit anything under \`infra/vault/\`.** Add it to