@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.9 → 0.1.12

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.
Files changed (41) hide show
  1. package/bin/adl +2 -2
  2. package/bin/git +1 -1
  3. package/bin/git-ado +2 -2
  4. package/bin/lib/bun-compile-build.sh +1 -1
  5. package/bin/lib/fleet-host.sh +59 -0
  6. package/bin/lib/lean-ctx-observe-hooks.sh +9 -9
  7. package/bin/reconcile +222 -249
  8. package/bin/reconcile-host +111 -0
  9. package/bin/setup +216 -0
  10. package/bin/sync-and-reconcile +52 -39
  11. package/bin/validate-manifest +15 -44
  12. package/package.json +1 -3
  13. package/packages/agent-hooks/package.json +1 -2
  14. package/packages/agent-hooks/src/hooks/crg/background.ts +179 -0
  15. package/packages/agent-hooks/src/hooks/crg/handler.ts +2 -1
  16. package/packages/agent-hooks/src/hooks/crg/index.ts +36 -5
  17. package/packages/agent-hooks/src/rules/impl/fs-trash.ts +2 -2
  18. package/packages/agent-hooks/src/rules/impl/git-paths.ts +5 -6
  19. package/packages/agent-hooks/src/rules/impl/tool-grep.ts +8 -8
  20. package/packages/agent-settings/assets/GLOBAL-AGENTS.md +4 -2
  21. package/packages/agent-settings/src/global-surface-sweep.ts +5 -15
  22. package/packages/agent-skills/package.json +1 -1
  23. package/packages/azdo/package.json +1 -2
  24. package/packages/azdo/src/azdo-repo-doctor.ts +70 -75
  25. package/packages/fleet/manifest.json +5 -24
  26. package/packages/fleet/manifest.schema.json +8 -58
  27. package/packages/fleet/src/install-embedding-model.ts +1 -1
  28. package/packages/fleet/src/repo-maintain.ts +403 -0
  29. package/packages/fleet/src/scratch-reap.ts +1 -1
  30. package/packages/repo-controls/README.md +46 -8
  31. package/packages/repo-controls/assets/promote.ts +12 -7
  32. package/packages/repo-controls/assets/reconcile-guard.sh +113 -0
  33. package/packages/repo-controls/assets/version-bump.sh +91 -51
  34. package/packages/repo-controls/package.json +0 -1
  35. package/packages/repo-controls/src/azure-devops-governance.ts +17 -16
  36. package/packages/repo-controls/src/constants.ts +8 -0
  37. package/packages/repo-controls/src/hook.ts +3 -2
  38. package/packages/repo-controls/src/integrity.ts +15 -1
  39. package/packages/fleet/jobs/git-maintain/src/main.ts +0 -848
  40. package/packages/fleet/systemd/adl-azure-agent-update.service +0 -13
  41. package/packages/fleet/systemd/adl-azure-agent-update.timer +0 -10
@@ -85,13 +85,24 @@
85
85
  * as its own finding. checkBuildServiceCanRelease() answers one narrow
86
86
  * question directly, per the real failure above: can the identity that
87
87
  * ACTUALLY RUNS THE PIPELINE (repo scope) push a `***NO_CI***` bump commit
88
- * to `integration` (GenericContribute), bypass `integration`'s own policies
89
- * while doing so (PolicyExempt), and create/update the `integration -> main`
90
- * PR and set auto-complete (PullRequestContribute) — and it FAILS loudly,
88
+ * to the PR's source (feature) branch (GenericContribute), and create/update
89
+ * the `integration -> main` PR and set auto-complete
90
+ * (PullRequestContribute) — and it FAILS loudly,
91
91
  * naming the permission, the identity's display name and the fix path, on
92
92
  * any of: the permission not effectively allowed, the identity not
93
93
  * resolvable, or the ACL itself not readable by the calling agent.
94
94
  *
95
+ * New release flow (Tarun, 2026-09-30): the CI pipeline runs on PRs into
96
+ * `integration` and its stage 1 pushes the version bump to the PR's SOURCE
97
+ * (feature) branch; a promote pipeline on `integration` pushes only opens the
98
+ * `integration -> main` PR; a CD pipeline runs on pushes to `main`. Nothing
99
+ * pushes directly to `integration` any more, so (a) the build service no
100
+ * longer needs PolicyExempt on `integration` — and, by least privilege,
101
+ * having it there is drift; (b) the release-identity check no longer needs
102
+ * PolicyExempt; (c) the promotion PR gets NO build validation (Tarun's
103
+ * approval is the only gate; CD runs after the merge), so `main` no longer
104
+ * requires a build-validation policy and an enabled one is drift.
105
+ *
95
106
  * Policy type GUIDs (verified against the azure-devops-cli-extension source,
96
107
  * packages/repo-controls already hardcodes the first three the same way):
97
108
  * minimum reviewers fa4e907d-c16b-4a4c-9dfa-4906e5d171dd
@@ -178,7 +189,11 @@ export const REPO_STANDARD = {
178
189
  forbiddenReviewerPolicies: [POLICY_TYPE.requiredReviewers] as const,
179
190
  mergeStrategy: { allowSquash: false, allowRebase: false, allowRebaseMerge: false, allowNoFastForward: true },
180
191
  commentRequirementsRequired: true,
181
- buildValidationRequiredWhenPipelineExists: true,
192
+ // Tarun, 2026-09-30, new release flow: the integration -> main promotion
193
+ // PR must NOT run a build (Tarun's approval is the only gate; the CD
194
+ // pipeline runs after the merge, on pushes to main). An ENABLED build
195
+ // policy on main is drift (`main.build-validation-present`).
196
+ buildValidationForbidden: true,
182
197
  },
183
198
  // One table for every effective-permission expectation this checker reads
184
199
  // via the Git Repositories security namespace ACL, so the standard never
@@ -216,9 +231,13 @@ export const REPO_STANDARD = {
216
231
  * — Tarun, 2026-09-27, cross-checked against
217
232
  * /Users/tarunrana/Documents/ADL-ISMS-Policies/.scratchpad/ci_build_service_grant.py,
218
233
  * which grants exactly these bits on adl-agent-control). CI needs to push
219
- * and complete PRs on the repo, and needs PolicyExempt only on
220
- * `integration` (so a version-bump commit made by CI itself is not
221
- * blocked by its own policies) — never on `main`.
234
+ * and complete PRs on the repo (Contribute is also what the version-bump
235
+ * push to the PR's SOURCE feature branch needs — Tarun, 2026-09-30, new
236
+ * release flow). It needs PolicyExempt NOWHERE: nothing pushes directly to
237
+ * `integration` any more (the bump lands on the feature branch), so
238
+ * PolicyExempt on `integration` is no longer required and, by least
239
+ * privilege, is drift (`permissions.build-service.has-policyExempt-integration`);
240
+ * never on `main` either (existing rule).
222
241
  *
223
242
  * NOT forcePush (Tarun, 2026-09-30): the bump is a plain fast-forward
224
243
  * `git push origin HEAD:integration`; agent-identities' build service has
@@ -229,33 +248,28 @@ export const REPO_STANDARD = {
229
248
  repo: { allow: ["genericContribute", "pullRequestContribute"] } as {
230
249
  allow: readonly PermissionBitName[];
231
250
  },
232
- integrationRef: { allow: ["policyExempt"] } as { allow: readonly PermissionBitName[] },
251
+ integrationRef: { deny: ["policyExempt"] } as { deny: readonly PermissionBitName[] },
233
252
  mainRef: { deny: ["policyExempt"] } as { deny: readonly PermissionBitName[] },
234
253
  },
235
254
  /**
236
255
  * checkBuildServiceCanRelease() (file header, "checkBuildServiceCanRelease()"):
237
- * the exact three permissions the release flow needs from whichever
238
- * build service identity actually runs the pipeline — the real failure
239
- * this check exists for was a 403 on `pullRequestContribute`.
256
+ * the permissions the release flow needs from whichever build service
257
+ * identity actually runs the pipeline — the real failure this check
258
+ * exists for was a 403 on `pullRequestContribute`.
240
259
  *
241
- * `policyExempt` ("Bypass policies when pushing") is checked at the
242
- * `integration` REF token, never the repo-scope token — verified live
243
- * 2026-09-30 against adl-agent-control: the Infrastructure-AzDo Build
244
- * Service demonstrably bypassed `integration`'s own policies to push
245
- * `chore: bump version 0.1.6 to 0.1.7 ***NO_CI***` (c966e6a) straight to
246
- * that protected branch, yet its repo-scope effective allow bitmask
247
- * (16430) does not include PolicyExempt (128) at all — only its
248
- * `refs/heads/integration`-scope effective allow (16558) does. Checking
249
- * `policyExempt` at repo scope produced a false
250
- * `permissions.release.<slug>.missing-policyExempt` finding that
251
- * contradicted this observed reality. `genericContribute` and
252
- * `pullRequestContribute` stay checked at repo scope — both bits ARE
253
- * present there for real, and PR operations are a repo-level, not
254
- * branch-level, permission.
260
+ * `policyExempt` ("Bypass policies when pushing") is NOT required any
261
+ * more (Tarun, 2026-09-30, new release flow): the version bump now lands
262
+ * on the PR's SOURCE (feature) branch, not on `integration`, so nothing
263
+ * needs to bypass `integration`'s policies. (History: it used to be
264
+ * checked at the `integration` REF token, never repo scope — verified
265
+ * live 2026-09-30 against adl-agent-control, where the build service
266
+ * pushed `chore: bump version 0.1.6 to 0.1.7 ***NO_CI***` (c966e6a)
267
+ * straight to `integration`.) `genericContribute` (the bump push to the
268
+ * feature branch) and `pullRequestContribute` stay checked at repo scope
269
+ * — PR operations are a repo-level, not branch-level, permission.
255
270
  */
256
271
  buildServiceRelease: {
257
272
  repoScoped: ["genericContribute", "pullRequestContribute"] as const,
258
- integrationRefScoped: ["policyExempt"] as const,
259
273
  },
260
274
  },
261
275
  } as const;
@@ -518,11 +532,7 @@ function checkMainMergeStrategy(policies: readonly PolicyConfiguration[], drifts
518
532
  });
519
533
  }
520
534
 
521
- function checkMainCommentsAndBuild(
522
- policies: readonly PolicyConfiguration[],
523
- hasPipeline: boolean,
524
- drifts: RepoDoctorDrift[],
525
- ): void {
535
+ function checkMainCommentsAndBuild(policies: readonly PolicyConfiguration[], drifts: RepoDoctorDrift[]): void {
526
536
  const comments = enabledPolicyOfType(policies, POLICY_TYPE.commentRequirements);
527
537
  if (!comments) {
528
538
  drifts.push({
@@ -531,21 +541,27 @@ function checkMainCommentsAndBuild(
531
541
  });
532
542
  }
533
543
 
534
- if (!hasPipeline) return;
544
+ // Tarun, 2026-09-30, new release flow: the promotion PR must not run a
545
+ // build — main no longer requires build validation, and an enabled one is
546
+ // drift (regardless of whether a pipeline exists).
535
547
  const build = enabledPolicyOfType(policies, POLICY_TYPE.build);
536
- if (!build) drifts.push({ id: "main.build-validation-missing", message: "missing build validation" });
548
+ if (build) {
549
+ drifts.push({
550
+ id: "main.build-validation-present",
551
+ message: `main has an enabled build-validation policy (#${build.id}); the promotion PR must not run a build (CD runs after merge) — Tarun's approval is the only gate`,
552
+ });
553
+ }
537
554
  }
538
555
 
539
556
  function checkMainPolicies(
540
557
  policies: readonly PolicyConfiguration[],
541
- hasPipeline: boolean,
542
558
  strictMainBranchRules: boolean,
543
559
  drifts: RepoDoctorDrift[],
544
560
  ): void {
545
561
  checkMainRequiredReviewer(policies, drifts);
546
562
  checkMainMinimumReviewers(policies, strictMainBranchRules, drifts);
547
563
  checkMainMergeStrategy(policies, drifts);
548
- checkMainCommentsAndBuild(policies, hasPipeline, drifts);
564
+ checkMainCommentsAndBuild(policies, drifts);
549
565
  }
550
566
 
551
567
  /**
@@ -823,10 +839,11 @@ async function resolveBuildServiceDescriptor(namespaceId: string, projectId: str
823
839
  }
824
840
 
825
841
  /**
826
- * CI needs to push and complete PRs on the repo itself, and needs to bypass
827
- * policy only on `integration` (so its own version-bump commit is not
828
- * blocked by the very policies it satisfies) — never on `main`, where a
829
- * bypass would defeat the human-reviewer gate entirely.
842
+ * CI needs to push (the version bump to the PR's source branch) and complete
843
+ * PRs on the repo itself. It needs to bypass policy NOWHERE (Tarun,
844
+ * 2026-09-30, new release flow: nothing pushes directly to `integration`
845
+ * any more, so PolicyExempt there is drift; on `main` a bypass would defeat
846
+ * the human-reviewer gate entirely).
830
847
  */
831
848
  async function checkBuildServicePermissions(
832
849
  namespaceId: string,
@@ -885,11 +902,14 @@ async function checkBuildServicePermissions(
885
902
  drifts,
886
903
  );
887
904
  if (integrationEffective) {
888
- for (const bitName of REPO_STANDARD.permissions.buildService.integrationRef.allow) {
889
- if ((effectiveAllowBits(integrationEffective) & GIT_REPO_PERMISSION_BIT[bitName]) === 0) {
905
+ // Tarun, 2026-09-30, new release flow: nothing pushes directly to
906
+ // integration any more, so PolicyExempt there is no longer required and
907
+ // is flagged when effectively allowed (least privilege).
908
+ for (const bitName of REPO_STANDARD.permissions.buildService.integrationRef.deny) {
909
+ if ((effectiveAllowBits(integrationEffective) & GIT_REPO_PERMISSION_BIT[bitName]) !== 0) {
890
910
  drifts.push({
891
- id: `permissions.build-service.missing-${bitName}-integration`,
892
- message: `the project build service does not effectively have ${bitName} on refs/heads/integration`,
911
+ id: `permissions.build-service.has-${bitName}-integration`,
912
+ message: `the project build service effectively has ${bitName} on refs/heads/integration, which the standard forbids (nothing pushes directly to integration; the version bump lands on the PR's source branch)`,
893
913
  });
894
914
  }
895
915
  }
@@ -941,16 +961,6 @@ function releaseFixPath(repository: string, identityDisplayName: string, bitName
941
961
  return `Project settings → Repositories → \`${repository}\` → Security → \`${identityDisplayName}\` → \`${GIT_REPO_PERMISSION_UI_LABEL[bitName]}\` = Allow`;
942
962
  }
943
963
 
944
- /** Same as releaseFixPath() but for a bit that is granted per-BRANCH (verified live 2026-09-30: PolicyExempt on this org's build service lives on refs/heads/integration's own security page, never the repo-root one). */
945
- function releaseFixPathForRef(
946
- repository: string,
947
- ref: string,
948
- identityDisplayName: string,
949
- bitName: PermissionBitName,
950
- ): string {
951
- return `Repos → \`${repository}\` → Branches → \`${ref}\` → ⋯ → Branch security → \`${identityDisplayName}\` → \`${GIT_REPO_PERMISSION_UI_LABEL[bitName]}\` = Allow`;
952
- }
953
-
954
964
  /**
955
965
  * Which build service identity actually runs the pipeline, per Build —
956
966
  * General Settings — Get's `enforceJobAuthScope` (file header) — "Limit job
@@ -973,12 +983,10 @@ async function fetchEnforceJobAuthScope(c: AdoClient): Promise<boolean | undefin
973
983
  /**
974
984
  * One identity's answer to "can it release": resolve its descriptor by exact
975
985
  * display name (Identities — Read Identities, file header), then check
976
- * `buildServiceRelease.repoScoped` on the repository token AND
977
- * `buildServiceRelease.integrationRefScoped` on the `integration` ref token
978
- * — NOT the same token for all three (see REPO_STANDARD.permissions.
979
- * buildServiceRelease's own doc comment: PolicyExempt is a per-BRANCH grant
980
- * in this org, verified live against real bypass-on-push evidence, and
981
- * checking it at repo scope produced a false "missing" finding). Every
986
+ * `buildServiceRelease.repoScoped` on the repository token. PolicyExempt is
987
+ * no longer part of what a release needs (Tarun, 2026-09-30, new release
988
+ * flow: the bump lands on the PR's source branch, not on integration — see
989
+ * REPO_STANDARD.permissions.buildServiceRelease's own doc comment). Every
982
990
  * failure mode is its own loud, distinct drift — an unresolvable identity,
983
991
  * an ACL the calling agent cannot read, and each missing permission named
984
992
  * with its fix path — never a silent pass.
@@ -986,7 +994,6 @@ async function fetchEnforceJobAuthScope(c: AdoClient): Promise<boolean | undefin
986
994
  async function checkOneBuildServiceCanRelease(
987
995
  namespaceId: string,
988
996
  repoToken: string,
989
- integrationRefToken: string,
990
997
  repository: string,
991
998
  displayName: string,
992
999
  drifts: RepoDoctorDrift[],
@@ -1013,8 +1020,7 @@ async function checkOneBuildServiceCanRelease(
1013
1020
  }
1014
1021
 
1015
1022
  // LOW, independent review 2026-09-30: name WHICH token failed — "the ACL"
1016
- // alone left a human unable to tell a repo-scope 403 from a
1017
- // refs/heads/integration-scope one without re-running this by hand.
1023
+ // alone left a human unable to tell which token's read 403'd.
1018
1024
  async function effectiveAt(token: string, tokenLabel: string): Promise<{ allow: number; deny: number } | undefined> {
1019
1025
  let effective: EffectivePermissionsResult;
1020
1026
  try {
@@ -1039,16 +1045,6 @@ async function checkOneBuildServiceCanRelease(
1039
1045
  message: `'${displayName}' does not effectively have ${GIT_REPO_PERMISSION_UI_LABEL[bitName]} on '${repository}' — the release pipeline will 403 the way agent-identities build 4897 did. Fix: ${releaseFixPath(repository, displayName, bitName)}`,
1040
1046
  });
1041
1047
  }
1042
-
1043
- const integrationEffective = await effectiveAt(integrationRefToken, "refs/heads/integration");
1044
- if (!integrationEffective) return;
1045
- for (const bitName of REPO_STANDARD.permissions.buildServiceRelease.integrationRefScoped) {
1046
- if ((effectiveAllowBits(integrationEffective) & GIT_REPO_PERMISSION_BIT[bitName]) !== 0) continue;
1047
- drifts.push({
1048
- id: `permissions.release.${slug}.missing-${bitName}`,
1049
- message: `'${displayName}' does not effectively have ${GIT_REPO_PERMISSION_UI_LABEL[bitName]} on 'refs/heads/integration' of '${repository}' — its own version-bump commit would be blocked by integration's own policies. Fix: ${releaseFixPathForRef(repository, "integration", displayName, bitName)}`,
1050
- });
1051
- }
1052
1048
  }
1053
1049
 
1054
1050
  /**
@@ -1079,9 +1075,8 @@ async function checkBuildServiceCanRelease(
1079
1075
  : [projectScoped, collectionScoped];
1080
1076
 
1081
1077
  const repoToken = `repoV2/${projectId}/${repositoryId}`;
1082
- const integrationRefToken = `${repoToken}/refs/heads/${refNameHexUtf16Le("integration")}`;
1083
1078
  for (const displayName of displayNames) {
1084
- await checkOneBuildServiceCanRelease(namespaceId, repoToken, integrationRefToken, repository, displayName, drifts);
1079
+ await checkOneBuildServiceCanRelease(namespaceId, repoToken, repository, displayName, drifts);
1085
1080
  }
1086
1081
  }
1087
1082
 
@@ -1140,7 +1135,7 @@ export async function repoDoctorCheck(options: RepoDoctorOptions = {}): Promise<
1140
1135
  const integrationPolicies = await fetchBranchPolicies(c, repositoryId, "integration");
1141
1136
  const mainPolicies = await fetchBranchPolicies(c, repositoryId, "main");
1142
1137
  checkIntegrationPolicies(integrationPolicies, hasPipeline, drifts);
1143
- checkMainPolicies(mainPolicies, hasPipeline, strictMainBranchRules, drifts);
1138
+ checkMainPolicies(mainPolicies, strictMainBranchRules, drifts);
1144
1139
 
1145
1140
  await checkAgentPermissions(projectId, repositoryId, strictMainBranchRules, drifts);
1146
1141
  await checkBuildServicePermissions(
@@ -3,7 +3,7 @@
3
3
  "schema": 1,
4
4
  "repository": "https://dev.azure.com/amsterdamdatalabs/Infrastructure-AzDo/_git/adl-agent-control",
5
5
  "branch": "main",
6
- "interval_seconds": 21600,
6
+ "interval_seconds": 43200,
7
7
  "update_policy": "latest-stable",
8
8
  "allow_major_updates": true,
9
9
  "remove_deleted_entries": true,
@@ -33,12 +33,6 @@
33
33
  "git_global_config": {
34
34
  "lfs.ssh://ssh.dev.azure.com/.sshtransfer": "never",
35
35
  "lfs.https://ssh.dev.azure.com/.locksverify": "false"
36
- },
37
- "azure_agents": {
38
- "organization": "amsterdamdatalabs",
39
- "pool_id": 1,
40
- "coordinator": "hostinger-vps-1",
41
- "enabled_agents": ["mac-m5-ci", "hostinger-vps-1-agent-1", "hostinger-vps-1-agent-2"]
42
36
  }
43
37
  },
44
38
  "mac": {
@@ -70,21 +64,6 @@
70
64
  "shfmt": {"manager": "brew", "package": "shfmt", "executable": "shfmt"},
71
65
  "uv": {"manager": "brew", "package": "uv", "executable": "uv"},
72
66
  "yq": {"manager": "brew", "package": "yq", "executable": "yq"}
73
- },
74
- "launch_agents": {
75
- "git-maintain": {
76
- "label": "com.adl.git-maintain",
77
- "source": "packages/fleet/jobs/git-maintain",
78
- "entry": "src/main.ts",
79
- "start_interval_seconds": 86400,
80
- "run_at_load": true,
81
- "repo_scope": {"root": "Documents", "glob": "*", "recursive": true},
82
- "environment": {
83
- "PATH": "/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin"
84
- },
85
- "log": "Library/Logs/adl/git-maintain.log",
86
- "replaces": [".local/bin/git-maintain.sh"]
87
- }
88
67
  }
89
68
  },
90
69
  "devices": {
@@ -94,6 +73,7 @@
94
73
  "home": "/Users/tarunrana",
95
74
  "minimum_free_gib": 20,
96
75
  "schedule_minute": 5,
76
+ "repo_location": "/Users/tarunrana/Documents/Repos",
97
77
  "brew_upgrade_all": true,
98
78
  "surface_clis": [
99
79
  {"name": "claude", "manager": "native", "command": "claude", "update_args": ["update"]},
@@ -114,6 +94,7 @@
114
94
  "home": "/Users/ranahome",
115
95
  "minimum_free_gib": 20,
116
96
  "schedule_minute": 35,
97
+ "repo_location": "/Users/ranahome/Documents",
117
98
  "brew_upgrade_all": true,
118
99
  "surface_clis": [
119
100
  {"name": "claude", "manager": "native", "command": "claude", "update_args": ["update"]},
@@ -169,7 +150,7 @@
169
150
  "minimum_free_gib": 25,
170
151
  "schedule_minute": 50,
171
152
  "scratch_repos": ["/root/enact-webview"],
172
- "git_maintain_repos": ["/opt/adl-azdo"],
153
+ "repo_location": "/root/workspace",
173
154
  "brew_upgrade_all": true,
174
155
  "surface_clis": [
175
156
  {"name": "claude", "manager": "native", "command": "claude", "update_args": ["update"]},
@@ -190,7 +171,7 @@
190
171
  "minimum_free_gib": 25,
191
172
  "schedule_minute": 20,
192
173
  "scratch_repos": ["/root/enact-webview"],
193
- "git_maintain_repos": ["/opt/adl-azdo"],
174
+ "repo_location": "/root/workspace",
194
175
  "brew_upgrade_all": true,
195
176
  "surface_clis": [
196
177
  {"name": "opencode", "manager": "npm", "command": "opencode", "update_args": ["upgrade"]},
@@ -8,7 +8,7 @@
8
8
  "properties": {
9
9
  "config": {
10
10
  "type": "object",
11
- "required": ["schema", "repository", "branch", "interval_seconds", "update_policy", "allow_major_updates", "remove_deleted_entries", "global_surface_sweep", "kimi_hooks", "surface_policy", "global_agents", "vscode_settings", "scratch_reap", "protected_tools", "git_global_config", "azure_agents"],
11
+ "required": ["schema", "repository", "branch", "interval_seconds", "update_policy", "allow_major_updates", "remove_deleted_entries", "global_surface_sweep", "kimi_hooks", "surface_policy", "global_agents", "vscode_settings", "scratch_reap", "protected_tools", "git_global_config"],
12
12
  "properties": {
13
13
  "global_surface_sweep": {
14
14
  "type": "object",
@@ -72,20 +72,8 @@
72
72
  }
73
73
  }
74
74
  },
75
- "mac": {
76
- "allOf": [{"$ref": "#/$defs/platform"}],
77
- "properties": {
78
- "common": {"properties": {"launch_agents": {"$ref": "#/$defs/launch_agents"}}},
79
- "devices": {"additionalProperties": {"properties": {"launch_agents": {"$ref": "#/$defs/launch_agents"}}}}
80
- }
81
- },
82
- "linux": {
83
- "allOf": [{"$ref": "#/$defs/platform"}],
84
- "properties": {
85
- "common": {"not": {"required": ["launch_agents"]}},
86
- "devices": {"additionalProperties": {"not": {"required": ["launch_agents"]}}}
87
- }
88
- }
75
+ "mac": {"$ref": "#/$defs/platform"},
76
+ "linux": {"$ref": "#/$defs/platform"}
89
77
  },
90
78
  "$defs": {
91
79
  "tool": {
@@ -157,7 +145,6 @@
157
145
  "type": "object",
158
146
  "additionalProperties": {"$ref": "#/$defs/tool"}
159
147
  },
160
- "relative_path": {"type": "string", "pattern": "^(?!/)(?!.*(^|/)\\.\\.(/|$)).+$"},
161
148
  "surface_cli": {
162
149
  "type": "object",
163
150
  "additionalProperties": false,
@@ -197,46 +184,13 @@
197
184
  }
198
185
  ]
199
186
  },
200
- "launch_agent": {
201
- "type": "object",
202
- "additionalProperties": false,
203
- "required": ["label", "source", "entry", "start_interval_seconds", "run_at_load", "environment", "log"],
204
- "properties": {
205
- "label": {"type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*$"},
206
- "source": {"$ref": "#/$defs/relative_path", "description": "Checkout-relative job directory; reconcile compiles it to a dedicated signed binary, rebuilt when its content hash changes."},
207
- "entry": {"$ref": "#/$defs/relative_path", "description": "TypeScript entry file relative to source."},
208
- "start_interval_seconds": {"type": "integer", "minimum": 60},
209
- "run_at_load": {"type": "boolean"},
210
- "repo_scope": {
211
- "type": "object",
212
- "additionalProperties": false,
213
- "required": ["root", "glob", "recursive"],
214
- "properties": {
215
- "root": {"$ref": "#/$defs/relative_path", "description": "Repository scan root relative to the device administrator's home."},
216
- "glob": {"type": "string", "minLength": 1},
217
- "recursive": {"const": true}
218
- }
219
- },
220
- "environment": {
221
- "type": "object",
222
- "propertyNames": {"pattern": "^[A-Z_][A-Z0-9_]*$"},
223
- "additionalProperties": {"type": "string"}
224
- },
225
- "log": {"$ref": "#/$defs/relative_path"},
226
- "replaces": {"type": "array", "items": {"$ref": "#/$defs/relative_path"}}
227
- }
228
- },
229
- "launch_agents": {
230
- "description": "User LaunchAgents enact-vps installs for the device administrator (macOS only).",
231
- "type": "object",
232
- "additionalProperties": {"$ref": "#/$defs/launch_agent"}
233
- },
234
187
  "platform": {
235
188
  "type": "object",
236
189
  "required": ["common", "devices"],
237
190
  "properties": {
238
191
  "common": {
239
192
  "type": "object",
193
+ "additionalProperties": false,
240
194
  "required": ["tools"],
241
195
  "properties": {
242
196
  "os_upgrade": {"type": "boolean"},
@@ -274,14 +228,10 @@
274
228
  "items": {"type": "string", "pattern": "^/[^\\s]*[^/\\s]$"},
275
229
  "uniqueItems": true
276
230
  },
277
- "git_maintain_repos": {
278
- "type": "array",
279
- "description": "An explicit, closed list of repositories bin/reconcile passes to git-maintain's --repo argv, for a device whose enrolled checkout sits at a fixed path outside any device's home (the Linux VPSes' /opt/adl-azdo) rather than under a recursively-scanned ~/Documents. Never a scan root: each entry is one exact repository path, so this can never walk into an unrelated directory the way scanning a shared parent like /opt could. Case-insensitive azagent/vsts.agent ban: git-maintain itself refuses the same names again, by realpath too, at the point of actual use.",
280
- "items": {
281
- "type": "string",
282
- "pattern": "^/(?!$)(?!.*\\s)(?!.*/$)(?!.*\\.\\.)(?!.*[aA][zZ][aA][gG][eE][nN][tT])(?!.*[vV][sS][tT][sS]\\.[aA][gG][eE][nN][tT]).+$"
283
- },
284
- "uniqueItems": true
231
+ "repo_location": {
232
+ "type": "string",
233
+ "description": "One absolute directory on this host. `adl reconcile` maintains (fetch --prune, fast-forward integration and main, safe branch prune, then `adl install --repo`) every IMMEDIATE child of it that carries adl-config.toml and is a git top-level, as the device administrator. It is the entire scope of that step: nothing outside it is scanned or touched, and a child that resolves outside it is refused. A missing or unreadable location fails the run; a host that declares none skips the step with a log line. The case-insensitive azagent/vsts.agent ban keeps it away from Azure Pipelines agent installs (JSON Schema `pattern` has no case-insensitive flag, so the classes are spelled out).",
234
+ "pattern": "^/(?!$)(?!.*\\s)(?!.*/$)(?!.*\\.\\.)(?!.*[aA][zZ][aA][gG][eE][nN][tT])(?!.*[vV][sS][tT][sS]\\.[aA][gG][eE][nN][tT]).+$"
285
235
  },
286
236
  "tools": {"$ref": "#/$defs/tools"}
287
237
  }
@@ -32,7 +32,7 @@
32
32
  * bytes because they merely arrived over HTTPS. `revision` is then recorded
33
33
  * in the installed `manifest.json` so the next run can skip the rebuild
34
34
  * (network round-trip to resolve latest + a local file read) instead of
35
- * re-downloading and re-verifying multi-hundred-MB files on every six-hour
35
+ * re-downloading and re-verifying multi-hundred-MB files on every 12-hour
36
36
  * reconcile just to reach the same answer.
37
37
  *
38
38
  * `manifest.json`'s `source`, `vocab_limit`, and `dim` come from