@amsterdamdatalabs/adl-agent-control-linux-x64 0.1.46 → 0.1.48
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/bin/adl +1 -1
- package/bin/reconcile +7 -2
- package/package.json +1 -1
- package/packages/agent-hooks/src/rules/impl/git-branch.ts +1 -1
- package/packages/agent-skills/assets/write-feature/SKILL.md +13 -15
- package/bin/lean-ctx-observe-strip +0 -434
- package/bin/lib/lean-ctx-observe-hooks.sh +0 -634
- package/bin/update-azure-agent-pool +0 -29
- package/packages/agent-hooks/src/hooks/lean-ctx/handler.ts +0 -22
- package/packages/agent-hooks/src/hooks/lean-ctx/index.ts +0 -173
- package/packages/agent-hooks/src/hooks/lean-ctx/mapping.ts +0 -121
- package/packages/agent-hooks/src/hooks/lean-ctx/scope-rewrite.ts +0 -207
- package/packages/agent-hooks/src/lean-ctx/env.ts +0 -123
- package/packages/agent-hooks/src/lean-ctx/mcp.ts +0 -142
- package/packages/agent-hooks/src/rules/impl/adl-build-run-denied.ts +0 -51
- package/packages/agent-hooks/src/rules/impl/adl-session-required.ts +0 -403
- package/packages/agent-hooks/src/rules/impl/tool-lean-ctx-global.ts +0 -594
- package/packages/agent-settings/assets/GLOBAL-AGENTS.md +0 -112
- package/packages/agent-settings/assets/lean-ctx/config.toml +0 -42
- package/packages/agent-settings/assets/lean-ctx/gitignore +0 -10
- package/packages/agent-settings/assets/lean-ctx/layout.toml +0 -7
- package/packages/agent-settings/src/deploy/lean-ctx-deploy.ts +0 -230
- package/packages/agent-settings/src/deploy/lean-ctx-merge.ts +0 -580
- package/packages/agent-skills/assets/adl-mechanics/SKILL.md +0 -38
- package/packages/agent-skills/assets/codebase-design/SKILL.md +0 -98
- package/packages/agent-skills/assets/codebase-design/references/DEEPENING.md +0 -37
- package/packages/agent-skills/assets/codebase-design/references/DESIGN-IT-TWICE.md +0 -44
- package/packages/agent-skills/assets/codebase-design/references/TESTABILITY.md +0 -29
- package/packages/agent-skills/assets/debug-issue/SKILL.md +0 -41
- package/packages/agent-skills/assets/domain-modeling/SKILL.md +0 -86
- package/packages/agent-skills/assets/domain-modeling/references/ADR-FORMAT.md +0 -47
- package/packages/agent-skills/assets/domain-modeling/references/CONTEXT-FORMAT.md +0 -60
- package/packages/agent-skills/assets/implement/SKILL.md +0 -28
- package/packages/agent-skills/assets/review-changes/SKILL.md +0 -45
- package/packages/agent-skills/assets/tdd/SKILL.md +0 -49
- package/packages/agent-skills/assets/tdd/references/mocking.md +0 -59
- package/packages/agent-skills/assets/tdd/references/tests.md +0 -77
- package/packages/agent-skills/assets/testing/SKILL.md +0 -54
- package/packages/agent-skills/assets/testing/references/e2e.md +0 -37
- package/packages/agent-skills/assets/testing/references/integration.md +0 -37
- package/packages/agent-skills/assets/testing/references/unit.md +0 -32
- package/packages/agent-skills/assets/to-feature/SKILL.md +0 -91
- package/packages/agent-skills/assets/to-feature/references/FEATURE-SPEC-TEMPLATE.md +0 -88
- package/packages/agent-skills/assets/to-workitems/SKILL.md +0 -90
- package/packages/agent-skills/assets/to-workitems/references/AZURE-DEVOPS-HIERARCHY.md +0 -121
- package/packages/agent-skills/assets/to-workitems/references/TICKET-TEMPLATES.md +0 -70
- package/packages/azdo/src/workflow-rules-matrix.csv +0 -85
- package/packages/fleet/src/launch-agents.ts +0 -649
- package/packages/repo-controls/assets/GLOBAL-INVARIANTS.md +0 -21
- package/packages/repo-controls/assets/enable-integration-auto-complete.mjs +0 -79
- package/packages/repo-controls/assets/scaffold/docs/notes.md +0 -20
- package/packages/repo-controls/assets/scaffold/plans/GAPS-AND-FINDINGS.md +0 -12
- package/packages/repo-controls/azure-pipelines.yml +0 -274
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/closed_set/fixtures/docs/notes.md +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/english_only/fixtures/excluded/frontend/mobile/src/i18n/locales/zh-CN/strings.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-clean/docs/notes.md +0 -17
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/okf_profile/fixtures/okf-ledger-no-lines/docs/notes.md +0 -13
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-delivery.md +0 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/stable-recommendation.md +0 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/docs/semantics/fixtures/semantics-violations/docs/architecture/product/target-narration.md +0 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-clean/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/a.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-empty-register/frontend/packages/desktop/src/b.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/fresh/fresh.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub1/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub10/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub11/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub2/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub3/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub4/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub5/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub6/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub7/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub8/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/many/sub9/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/services/i18n/locales/de/common.json +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-excluded/frontend/packages/desktop/src/vendored-crate/Cargo.toml +0 -2
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-missing-tree/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/a.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/b.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-no-ceilings/frontend/packages/desktop/src/c.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-stale-entry/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/fresh/fresh.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub1/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub10/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub11/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub2/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub3/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub4/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub5/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub6/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub7/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub8/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/many/sub9/only.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unbaselined/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/directory_ratchet/fixtures/directory-ratchet-unusable-register/frontend/packages/desktop/src/renderer/solo/item.ts +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/GAPS-AND-FINDINGS.md +0 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/open_work_cap/fixtures/open-work-cap-clean/plans/features/az601-clean/spec.md +0 -10
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/agent/crates/enact-providers/src/snapshots/README.md +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-app/assets/builtin-assistants/avatars/assistant.jpg +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-assets/assets/logos/tools/coding/tool.png +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/backend/crates/enact-session/src/backend/codex_init_prompt.md +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/mobile/assets/images/icon.png +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/logos/brand/app.png +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/packages/desktop/src/renderer/assets/themes/default-theme.png +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/public/pwa/icon-192.png +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-allowed/frontend/resources/product.png +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/placement/fixtures/placement-stray/backend/crates/enact-session/src/backend/README.md +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-clean/plans/features/az347-test-coverage/spec.md +0 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/plans_layout/fixtures/plans-layout-violation/plans/features/az347-test-coverage/spec.md +0 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/__init__.py +0 -11
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/check.py +0 -62
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/NotAMigration.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/0001_long_prefix.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/001_init.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/002_add_index.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/003-dashed-slug.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/004_MixedCase.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/005_trailing space.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/01_short_prefix.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/fixtures/migration-filenames/backend/crates/demo/migrations/not-a-migration.sql +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/profile_data/test_check.py +0 -116
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/links.md +0 -20
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-broken/docs/architecture/product/route-target.md +0 -8
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/links.md +0 -20
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/references/fixtures/link-targets-clean/docs/architecture/product/route-target.md +0 -8
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_closed_set/fixtures/.changeset/declared.txt +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/layout/root_markdown_allowlist/fixtures/root-markdown-clean/INDEPENDENT-VERIFICATION.md +0 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/.gitattributes +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/attribute-without-conversion/large file.bin +0 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/.gitattributes +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/large file.bin +0 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/clean/small.txt +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/missing-filter/large file.bin +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/.gitattributes +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/lfs/integrity/fixtures/unmatched-extension/payload.dat +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/__init__.py +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/__init__.py +0 -12
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/check.py +0 -129
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/disjoint/gate-registry.yaml +0 -13
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/empty/gate-registry.yaml +0 -7
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/fixtures/overlapping/gate-registry.yaml +0 -13
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/pipeline/phase_disjoint/test_check.py +0 -109
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/__init__.py +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/__init__.py +0 -11
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/check.py +0 -149
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/clean/docs/readme.md +0 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/fixtures/relevant/VERSION +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/changeset_plan/test_check.py +0 -151
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/__init__.py +0 -11
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/check.py +0 -202
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/changesets/package.json +0 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/.workflows/package.json +0 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/CHANGELOG.md +0 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/aligned/VERSION +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/changesets/package.json +0 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/.workflows/package.json +0 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/CHANGELOG.md +0 -5
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/fixtures/mismatched/VERSION +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/release/version_release/test_check.py +0 -162
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/banned_commands/check.py +0 -103
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.lock +0 -7
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/Cargo.toml +0 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/rust-toolchain.toml +0 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/backend/src/main.rs +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/clean/frontend/bun.lock +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.lock +0 -7
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/Cargo.toml +0 -4
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/rust-toolchain.toml +0 -3
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/backend/src/main.rs +0 -1
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/checks/repository/lockfile_integrity/fixtures/empty-bun-lock/frontend/bun.lock +0 -0
- package/packages/repo-controls/package/adl-compliance/src/adl_compliance/policy/grants-registry.yaml +0 -112
- package/packages/repo-controls/workflows/scripts/release/verify-npm-artifact.ts +0 -119
|
@@ -1,649 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* User LaunchAgents owned by adl.
|
|
3
|
-
*
|
|
4
|
-
* `packages/fleet/manifest.json` declares them under `mac.common.launch_agents`
|
|
5
|
-
* (and optionally `mac.devices.<device>.launch_agents`; validate-manifest
|
|
6
|
-
* rejects them on linux). For each declared job, reconcile (as root, apply):
|
|
7
|
-
*
|
|
8
|
-
* 1. compiles the job's TypeScript entry (`source`/`entry`) with
|
|
9
|
-
* `bun build --compile` into the dedicated binary
|
|
10
|
-
* `/usr/local/libexec/adl/<job id>` (root-owned, 0755 --
|
|
11
|
-
* never user-writable, since launchd runs it on a schedule), ad-hoc
|
|
12
|
-
* signed with the stable identifier `<label>` and the explicit designated
|
|
13
|
-
* requirement `identifier "<label>"`. It rebuilds only when a content
|
|
14
|
-
* hash of the source directory changes (or the binary is missing).
|
|
15
|
-
*
|
|
16
|
-
* Why a dedicated binary: under launchd the program in ProgramArguments
|
|
17
|
-
* is the responsible process for macOS privacy protection (TCC), and the
|
|
18
|
-
* `git` children it spawns are attributed to it. A job run by /bin/bash
|
|
19
|
-
* would need Full Disk Access granted to every bash script; a dedicated
|
|
20
|
-
* binary lets the owner grant exactly that one path. TCC matches a grant
|
|
21
|
-
* against the code's designated requirement; plain ad-hoc signing pins it
|
|
22
|
-
* to the cdhash (TN3127), so every rebuild would silently void the grant.
|
|
23
|
-
* The explicit identifier requirement keeps rebuilds satisfying it; the
|
|
24
|
-
* grant itself is still keyed to this root-owned path;
|
|
25
|
-
* 2. renders `~/Library/LaunchAgents/<label>.plist` for the device's
|
|
26
|
-
* administrator, expanding a leading `$HOME` in environment values and the
|
|
27
|
-
* home-relative `log` path (launchd itself expands nothing);
|
|
28
|
-
* 3. replaces any differing declared plist from source and removes every
|
|
29
|
-
* retired `replaces` path outright;
|
|
30
|
-
* 4. (re)loads the job in `gui/<uid>` when anything changed or it is not
|
|
31
|
-
* loaded. Without a GUI session for that user the job is left installed
|
|
32
|
-
* and launchd loads it at the next login (RunAtLoad applies then).
|
|
33
|
-
*
|
|
34
|
-
* A job present in the previously applied manifest but absent now is booted
|
|
35
|
-
* out, its plist and binary and source hash removed. The libexec dir itself
|
|
36
|
-
* is shared with other managers (e.g. `repo-dir` targets such as
|
|
37
|
-
* `agent-skills`/`repo-controls`/`agent-hooks`), so it is never swept
|
|
38
|
-
* wholesale: only an entry whose id this component currently declares, or
|
|
39
|
-
* once built and recorded a `<id>.source-hash` marker for under `stateDir`,
|
|
40
|
-
* is eligible for stale removal. Anything else in the dir -- another
|
|
41
|
-
* manager's target, a hand-placed file -- is left untouched.
|
|
42
|
-
*
|
|
43
|
-
* When a job's last log line is a TCC denial (git-maintain's
|
|
44
|
-
* `TCC_DENIED_MARKER`), plan and apply surface it verbatim, remedy included.
|
|
45
|
-
*
|
|
46
|
-
* `global-surface-sweep` derives LaunchAgent protection from the same
|
|
47
|
-
* declarations (`declaredLaunchAgentPlists`), so a shipped job is never a
|
|
48
|
-
* sweep candidate.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
import { spawnSync } from 'node:child_process'
|
|
52
|
-
import { createHash } from 'node:crypto'
|
|
53
|
-
import {
|
|
54
|
-
chmodSync,
|
|
55
|
-
chownSync,
|
|
56
|
-
existsSync,
|
|
57
|
-
lstatSync,
|
|
58
|
-
mkdirSync,
|
|
59
|
-
readFileSync,
|
|
60
|
-
readdirSync,
|
|
61
|
-
renameSync,
|
|
62
|
-
rmSync,
|
|
63
|
-
statSync,
|
|
64
|
-
writeFileSync,
|
|
65
|
-
} from 'node:fs'
|
|
66
|
-
import { dirname, isAbsolute, join, relative, resolve } from 'node:path'
|
|
67
|
-
import { TCC_DENIED_MARKER } from '../jobs/git-maintain/src/main.ts'
|
|
68
|
-
|
|
69
|
-
export const DEFAULT_LIBEXEC_DIR = '/usr/local/libexec/adl'
|
|
70
|
-
const LAUNCH_AGENTS_DIR = 'Library/LaunchAgents'
|
|
71
|
-
|
|
72
|
-
export interface RepositoryScope {
|
|
73
|
-
readonly root: string
|
|
74
|
-
readonly glob: string
|
|
75
|
-
readonly recursive: true
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface LaunchAgentSpec {
|
|
79
|
-
readonly label: string
|
|
80
|
-
/** Checkout-relative job directory; its content hash decides rebuilds. */
|
|
81
|
-
readonly source: string
|
|
82
|
-
/** Entry file relative to `source`. */
|
|
83
|
-
readonly entry: string
|
|
84
|
-
readonly start_interval_seconds: number
|
|
85
|
-
readonly run_at_load: boolean
|
|
86
|
-
readonly repo_scope?: RepositoryScope
|
|
87
|
-
readonly environment: Readonly<Record<string, string>>
|
|
88
|
-
readonly log: string
|
|
89
|
-
readonly replaces: readonly string[]
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export type Platform = 'mac' | 'linux'
|
|
93
|
-
|
|
94
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
95
|
-
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function decodeRepoScope(id: string, raw: unknown): RepositoryScope {
|
|
99
|
-
if (
|
|
100
|
-
!isRecord(raw) ||
|
|
101
|
-
typeof raw.root !== 'string' ||
|
|
102
|
-
raw.root.length === 0 ||
|
|
103
|
-
typeof raw.glob !== 'string' ||
|
|
104
|
-
raw.glob.length === 0 ||
|
|
105
|
-
raw.recursive !== true
|
|
106
|
-
) {
|
|
107
|
-
throw new Error(`invalid launch agent repo_scope: ${id} (need root: string, glob: string, recursive: true)`)
|
|
108
|
-
}
|
|
109
|
-
return { root: raw.root, glob: raw.glob, recursive: true }
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function decodeSpec(id: string, raw: unknown): LaunchAgentSpec {
|
|
113
|
-
if (
|
|
114
|
-
!isRecord(raw) ||
|
|
115
|
-
typeof raw.label !== 'string' ||
|
|
116
|
-
typeof raw.source !== 'string' ||
|
|
117
|
-
typeof raw.entry !== 'string' ||
|
|
118
|
-
raw.script !== undefined ||
|
|
119
|
-
typeof raw.start_interval_seconds !== 'number' ||
|
|
120
|
-
typeof raw.run_at_load !== 'boolean' ||
|
|
121
|
-
!isRecord(raw.environment) ||
|
|
122
|
-
typeof raw.log !== 'string' ||
|
|
123
|
-
(raw.replaces !== undefined && !Array.isArray(raw.replaces))
|
|
124
|
-
) {
|
|
125
|
-
throw new Error(`invalid launch agent declaration: ${id}`)
|
|
126
|
-
}
|
|
127
|
-
const environment: Record<string, string> = {}
|
|
128
|
-
for (const [key, value] of Object.entries(raw.environment)) {
|
|
129
|
-
if (typeof value !== 'string') throw new Error(`invalid launch agent environment value: ${id}.${key}`)
|
|
130
|
-
environment[key] = value
|
|
131
|
-
}
|
|
132
|
-
const replaces = ((raw.replaces ?? []) as unknown[]).map((entry) => {
|
|
133
|
-
if (typeof entry !== 'string') throw new Error(`invalid launch agent replaces entry: ${id}`)
|
|
134
|
-
return entry
|
|
135
|
-
})
|
|
136
|
-
const repo_scope = raw.repo_scope === undefined ? undefined : decodeRepoScope(id, raw.repo_scope)
|
|
137
|
-
return {
|
|
138
|
-
label: raw.label,
|
|
139
|
-
source: raw.source,
|
|
140
|
-
entry: raw.entry,
|
|
141
|
-
start_interval_seconds: raw.start_interval_seconds,
|
|
142
|
-
run_at_load: raw.run_at_load,
|
|
143
|
-
...(repo_scope === undefined ? {} : { repo_scope }),
|
|
144
|
-
environment,
|
|
145
|
-
log: raw.log,
|
|
146
|
-
replaces,
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function agentsAt(value: unknown, id: string): Record<string, LaunchAgentSpec> {
|
|
151
|
-
if (value === undefined) return {}
|
|
152
|
-
if (!isRecord(value)) throw new Error(`launch_agents must be an object: ${id}`)
|
|
153
|
-
const result: Record<string, LaunchAgentSpec> = {}
|
|
154
|
-
for (const [key, raw] of Object.entries(value)) result[key] = decodeSpec(key, raw)
|
|
155
|
-
return result
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** `<platform>.common.launch_agents` merged with `<platform>.devices.<device>.launch_agents` (device wins). */
|
|
159
|
-
export function resolveLaunchAgents(manifest: unknown, platform: Platform, device: string): Record<string, LaunchAgentSpec> {
|
|
160
|
-
if (!isRecord(manifest)) throw new Error('manifest must be an object')
|
|
161
|
-
const section = manifest[platform]
|
|
162
|
-
if (!isRecord(section) || !isRecord(section.common) || !isRecord(section.devices)) {
|
|
163
|
-
throw new Error(`manifest has no ${platform} section`)
|
|
164
|
-
}
|
|
165
|
-
const deviceEntry = section.devices[device]
|
|
166
|
-
if (!isRecord(deviceEntry)) throw new Error(`device ${device} is not declared under ${platform}`)
|
|
167
|
-
return {
|
|
168
|
-
...agentsAt(section.common.launch_agents, `${platform}.common`),
|
|
169
|
-
...agentsAt(deviceEntry.launch_agents, `${platform}.devices.${device}`),
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Every LaunchAgent plist (home-relative) any mac device in the manifest
|
|
175
|
-
* declares, with its declaration path. Protection needs only each label, so a
|
|
176
|
-
* job's build fields never gate it; an entry without a valid label fails loud.
|
|
177
|
-
*/
|
|
178
|
-
export function declaredLaunchAgentPlists(manifest: unknown): readonly { readonly path: string; readonly declaredAt: string }[] {
|
|
179
|
-
if (!isRecord(manifest) || !isRecord(manifest.mac)) return []
|
|
180
|
-
const mac = manifest.mac
|
|
181
|
-
const found = new Map<string, string>()
|
|
182
|
-
const collect = (value: unknown, where: string): void => {
|
|
183
|
-
if (value === undefined) return
|
|
184
|
-
if (!isRecord(value)) throw new Error(`launch_agents must be an object: ${where}`)
|
|
185
|
-
for (const [id, raw] of Object.entries(value)) {
|
|
186
|
-
if (!isRecord(raw) || typeof raw.label !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(raw.label)) {
|
|
187
|
-
throw new Error(`invalid launch agent label: mac.${where}.launch_agents.${id}`)
|
|
188
|
-
}
|
|
189
|
-
const path = `${LAUNCH_AGENTS_DIR}/${raw.label}.plist`
|
|
190
|
-
if (!found.has(path)) found.set(path, `mac.${where}.launch_agents.${id}`)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (isRecord(mac.common)) collect(mac.common.launch_agents, 'common')
|
|
194
|
-
if (isRecord(mac.devices)) {
|
|
195
|
-
for (const [device, entry] of Object.entries(mac.devices)) {
|
|
196
|
-
if (isRecord(entry)) collect(entry.launch_agents, `devices.${device}`)
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
return [...found.entries()].map(([path, declaredAt]) => ({ path, declaredAt })).sort((a, b) => a.path.localeCompare(b.path))
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function xml(value: string): string {
|
|
203
|
-
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"')
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
function expandHome(value: string, home: string): string {
|
|
207
|
-
if (value === '$HOME') return home
|
|
208
|
-
if (value.startsWith('$HOME/')) return `${home}${value.slice('$HOME'.length)}`
|
|
209
|
-
return value
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
/** `<platform>` launch agent labels by job id, tolerating older declaration shapes (removal needs only the label). */
|
|
213
|
-
function resolveLabels(manifest: unknown, platform: Platform, device: string): Record<string, string> {
|
|
214
|
-
const labels: Record<string, string> = {}
|
|
215
|
-
if (!isRecord(manifest) || !isRecord(manifest[platform])) return labels
|
|
216
|
-
const section = manifest[platform] as Record<string, unknown>
|
|
217
|
-
const collect = (value: unknown): void => {
|
|
218
|
-
if (!isRecord(value)) return
|
|
219
|
-
for (const [id, raw] of Object.entries(value)) {
|
|
220
|
-
if (isRecord(raw) && typeof raw.label === 'string') labels[id] = raw.label
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
if (isRecord(section.common)) collect(section.common.launch_agents)
|
|
224
|
-
if (isRecord(section.devices) && isRecord(section.devices[device])) collect(section.devices[device].launch_agents)
|
|
225
|
-
return labels
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
export function installedProgramPath(id: string, libexecDir: string): string {
|
|
229
|
-
return join(libexecDir, id)
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function sourceHashPath(stateDir: string, id: string): string {
|
|
233
|
-
return join(stateDir, 'launch-agents', `${id}.source-hash`)
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const SOURCE_HASH_SUFFIX = '.source-hash'
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* ids this component may treat as its own in `libexecDir`: every currently
|
|
240
|
-
* declared job, plus any id it previously built and still holds a
|
|
241
|
-
* `<id>.source-hash` marker for in `stateDir`. An id with neither is not
|
|
242
|
-
* ours to remove, however stale-looking it is -- it belongs to whatever
|
|
243
|
-
* other manager (or hand-placed file) put it there.
|
|
244
|
-
*/
|
|
245
|
-
function ownedProgramIds(stateDir: string, currentIds: ReadonlySet<string>): Set<string> {
|
|
246
|
-
const owned = new Set(currentIds)
|
|
247
|
-
let markers: string[]
|
|
248
|
-
try {
|
|
249
|
-
markers = readdirSync(join(stateDir, 'launch-agents'))
|
|
250
|
-
} catch {
|
|
251
|
-
markers = []
|
|
252
|
-
}
|
|
253
|
-
for (const name of markers) {
|
|
254
|
-
if (name.endsWith(SOURCE_HASH_SUFFIX)) owned.add(name.slice(0, -SOURCE_HASH_SUFFIX.length))
|
|
255
|
-
}
|
|
256
|
-
return owned
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function listFiles(dir: string): string[] {
|
|
260
|
-
const out: string[] = []
|
|
261
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
262
|
-
if (entry.name === 'node_modules' || entry.name.endsWith('.bun-build')) continue
|
|
263
|
-
const path = join(dir, entry.name)
|
|
264
|
-
if (entry.isDirectory()) out.push(...listFiles(path))
|
|
265
|
-
else if (entry.isFile()) out.push(path)
|
|
266
|
-
}
|
|
267
|
-
return out
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/** sha256 over every file (relative path + content) in the job's source directory. */
|
|
271
|
-
export function sourceHash(sourceDir: string): string {
|
|
272
|
-
const hash = createHash('sha256')
|
|
273
|
-
for (const file of listFiles(sourceDir).sort()) {
|
|
274
|
-
hash.update(`${relative(sourceDir, file)}\0`)
|
|
275
|
-
hash.update(readFileSync(file))
|
|
276
|
-
hash.update('\0')
|
|
277
|
-
}
|
|
278
|
-
return hash.digest('hex')
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
export interface ProgramBuilder {
|
|
282
|
-
/** Compile `entryPath` to `outputPath` (a fresh file) signed with `identifier`; throw on failure. */
|
|
283
|
-
build(entryPath: string, outputPath: string, identifier: string): void
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* `bun build --compile` through the shared bun-compile-build.sh (throwaway
|
|
288
|
-
* CWD, so no `*.bun-build` leaks), then an ad-hoc re-sign with the stable
|
|
289
|
-
* identifier as an explicit designated requirement.
|
|
290
|
-
*/
|
|
291
|
-
export function systemProgramBuilder(bunBin: string, buildLibPath: string): ProgramBuilder {
|
|
292
|
-
return {
|
|
293
|
-
build: (entryPath, outputPath, identifier) => {
|
|
294
|
-
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(identifier)) throw new Error(`unsafe signing identifier: ${identifier}`)
|
|
295
|
-
const compiled = spawnSync(
|
|
296
|
-
'/bin/bash',
|
|
297
|
-
['-c', 'set -euo pipefail; source "$1"; bun_compile_build "$2" "$3" "$4"', 'bun-compile', buildLibPath, bunBin, entryPath, outputPath],
|
|
298
|
-
{ encoding: 'utf8' },
|
|
299
|
-
)
|
|
300
|
-
if (compiled.status !== 0) {
|
|
301
|
-
throw new Error(`bun build --compile ${entryPath} failed: ${`${compiled.stdout}${compiled.stderr}`.trim()}`)
|
|
302
|
-
}
|
|
303
|
-
const signed = spawnSync(
|
|
304
|
-
'codesign',
|
|
305
|
-
['--force', '--sign', '-', '--identifier', identifier, `-r=designated => identifier "${identifier}"`, outputPath],
|
|
306
|
-
{ encoding: 'utf8' },
|
|
307
|
-
)
|
|
308
|
-
if (signed.status !== 0) throw new Error(`codesign ${outputPath} failed: ${`${signed.stdout}${signed.stderr}`.trim()}`)
|
|
309
|
-
},
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/** The job's last log line when it is a TCC denial, so reconcile output carries the remedy. */
|
|
314
|
-
function lastRunDenial(spec: LaunchAgentSpec, home: string): string | null {
|
|
315
|
-
const content = readIfFile(join(home, spec.log))
|
|
316
|
-
if (content === null) return null
|
|
317
|
-
const last = content.split('\n').filter((line) => line.length > 0).at(-1)
|
|
318
|
-
return last !== undefined && last.includes(TCC_DENIED_MARKER) ? `launch-agent: ${spec.label} last run: ${last}` : null
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function renderLaunchAgentPlist(
|
|
322
|
-
spec: LaunchAgentSpec,
|
|
323
|
-
options: { readonly home: string; readonly programPath: string },
|
|
324
|
-
): string {
|
|
325
|
-
const logPath = join(options.home, spec.log)
|
|
326
|
-
const environment = Object.keys(spec.environment)
|
|
327
|
-
.sort()
|
|
328
|
-
.flatMap((key) => [
|
|
329
|
-
` <key>${xml(key)}</key>`,
|
|
330
|
-
` <string>${xml(expandHome(spec.environment[key]!, options.home))}</string>`,
|
|
331
|
-
])
|
|
332
|
-
return [
|
|
333
|
-
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
334
|
-
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
335
|
-
'<!-- Generated by adl global-tools (manifest launch_agents) -- do not edit; reconcile overwrites it. -->',
|
|
336
|
-
'<plist version="1.0">',
|
|
337
|
-
'<dict>',
|
|
338
|
-
' <key>Label</key>',
|
|
339
|
-
` <string>${xml(spec.label)}</string>`,
|
|
340
|
-
' <key>ProgramArguments</key>',
|
|
341
|
-
' <array>',
|
|
342
|
-
` <string>${xml(options.programPath)}</string>`,
|
|
343
|
-
' </array>',
|
|
344
|
-
' <key>EnvironmentVariables</key>',
|
|
345
|
-
' <dict>',
|
|
346
|
-
...environment,
|
|
347
|
-
' </dict>',
|
|
348
|
-
' <key>RunAtLoad</key>',
|
|
349
|
-
spec.run_at_load ? ' <true/>' : ' <false/>',
|
|
350
|
-
' <key>StartInterval</key>',
|
|
351
|
-
` <integer>${spec.start_interval_seconds}</integer>`,
|
|
352
|
-
' <key>StandardOutPath</key>',
|
|
353
|
-
` <string>${xml(logPath)}</string>`,
|
|
354
|
-
' <key>StandardErrorPath</key>',
|
|
355
|
-
` <string>${xml(logPath)}</string>`,
|
|
356
|
-
' <key>ProcessType</key>',
|
|
357
|
-
' <string>Background</string>',
|
|
358
|
-
' <key>LowPriorityIO</key>',
|
|
359
|
-
' <true/>',
|
|
360
|
-
' <key>Nice</key>',
|
|
361
|
-
' <integer>10</integer>',
|
|
362
|
-
'</dict>',
|
|
363
|
-
'</plist>',
|
|
364
|
-
'',
|
|
365
|
-
].join('\n')
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
export interface Launchctl {
|
|
369
|
-
hasGuiDomain(uid: number): boolean
|
|
370
|
-
isLoaded(uid: number, label: string): boolean
|
|
371
|
-
/** Unload; a job that is not loaded is the harmless common case. */
|
|
372
|
-
bootout(uid: number, label: string): void
|
|
373
|
-
bootstrap(uid: number, plistPath: string): { readonly ok: boolean; readonly detail: string }
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
export const systemLaunchctl: Launchctl = {
|
|
377
|
-
hasGuiDomain: (uid) => spawnSync('launchctl', ['print', `gui/${uid}`], { encoding: 'utf8' }).status === 0,
|
|
378
|
-
isLoaded: (uid, label) => spawnSync('launchctl', ['print', `gui/${uid}/${label}`], { encoding: 'utf8' }).status === 0,
|
|
379
|
-
bootout: (uid, label) => {
|
|
380
|
-
spawnSync('launchctl', ['bootout', `gui/${uid}/${label}`], { encoding: 'utf8' })
|
|
381
|
-
},
|
|
382
|
-
bootstrap: (uid, plistPath) => {
|
|
383
|
-
const result = spawnSync('launchctl', ['bootstrap', `gui/${uid}`, plistPath], { encoding: 'utf8' })
|
|
384
|
-
return { ok: result.status === 0, detail: `${result.stdout}${result.stderr}`.trim() }
|
|
385
|
-
},
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
export interface ApplyOptions {
|
|
389
|
-
readonly manifest: unknown
|
|
390
|
-
readonly previousManifest: unknown | null
|
|
391
|
-
readonly platform: Platform
|
|
392
|
-
readonly device: string
|
|
393
|
-
readonly home: string
|
|
394
|
-
readonly repoRoot: string
|
|
395
|
-
readonly libexecDir: string
|
|
396
|
-
readonly stateDir: string
|
|
397
|
-
readonly launchctl: Launchctl
|
|
398
|
-
readonly builder: ProgramBuilder
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
export interface ApplyResult {
|
|
402
|
-
readonly lines: readonly string[]
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
function readIfFile(path: string): string | null {
|
|
406
|
-
try {
|
|
407
|
-
if (!lstatSync(path).isFile()) return null
|
|
408
|
-
} catch {
|
|
409
|
-
return null
|
|
410
|
-
}
|
|
411
|
-
return readFileSync(path, 'utf8')
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function atomicWrite(path: string, content: string, mode: number, owner: { uid: number; gid: number } | null): void {
|
|
415
|
-
mkdirSync(dirname(path), { recursive: true })
|
|
416
|
-
const temporary = `${path}.adl-new`
|
|
417
|
-
writeFileSync(temporary, content, { mode })
|
|
418
|
-
chmodSync(temporary, mode)
|
|
419
|
-
if (owner !== null) chownSync(temporary, owner.uid, owner.gid)
|
|
420
|
-
renameSync(temporary, path)
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function ensureOwnedDir(path: string, owner: { uid: number; gid: number } | null): void {
|
|
424
|
-
mkdirSync(path, { recursive: true })
|
|
425
|
-
if (owner !== null) chownSync(path, owner.uid, owner.gid)
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
function needsBuild(id: string, spec: LaunchAgentSpec, options: { repoRoot: string; libexecDir: string; stateDir: string }): string | null {
|
|
429
|
-
const hash = sourceHash(join(options.repoRoot, spec.source))
|
|
430
|
-
const programPath = installedProgramPath(id, options.libexecDir)
|
|
431
|
-
const recorded = readIfFile(sourceHashPath(options.stateDir, id))?.trim()
|
|
432
|
-
return recorded === hash && existsSync(programPath) ? null : hash
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
export function planLaunchAgents(options: Omit<ApplyOptions, 'launchctl' | 'builder'>): readonly string[] {
|
|
436
|
-
const lines: string[] = []
|
|
437
|
-
const agents = resolveLaunchAgents(options.manifest, options.platform, options.device)
|
|
438
|
-
for (const [id, spec] of Object.entries(agents)) {
|
|
439
|
-
const plistPath = join(options.home, LAUNCH_AGENTS_DIR, `${spec.label}.plist`)
|
|
440
|
-
const programPath = installedProgramPath(id, options.libexecDir)
|
|
441
|
-
lines.push(`would install launch agent: ${spec.label} -> ${plistPath} (program ${programPath})`)
|
|
442
|
-
if (readIfFile(join(options.repoRoot, spec.source, spec.entry)) === null) {
|
|
443
|
-
lines.push(`launch agent job entry missing from checkout: ${join(spec.source, spec.entry)}`)
|
|
444
|
-
} else {
|
|
445
|
-
const hash = needsBuild(id, spec, options)
|
|
446
|
-
if (hash !== null) lines.push(`would build ${programPath} (source ${hash})`)
|
|
447
|
-
}
|
|
448
|
-
for (const legacy of spec.replaces) {
|
|
449
|
-
if (existsSync(join(options.home, legacy))) lines.push(`would remove retired ${join(options.home, legacy)}`)
|
|
450
|
-
}
|
|
451
|
-
const denial = lastRunDenial(spec, options.home)
|
|
452
|
-
if (denial !== null) lines.push(denial)
|
|
453
|
-
}
|
|
454
|
-
if (options.previousManifest !== null) {
|
|
455
|
-
const current = new Set(Object.values(agents).map((spec) => spec.label))
|
|
456
|
-
for (const label of Object.values(resolveLabels(options.previousManifest, options.platform, options.device))) {
|
|
457
|
-
if (!current.has(label)) lines.push(`would remove launch agent: ${label}`)
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
return lines
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
export function applyLaunchAgents(options: ApplyOptions): ApplyResult {
|
|
464
|
-
for (const [label, value] of [
|
|
465
|
-
['home', options.home],
|
|
466
|
-
['repo root', options.repoRoot],
|
|
467
|
-
['libexec dir', options.libexecDir],
|
|
468
|
-
['state dir', options.stateDir],
|
|
469
|
-
] as const) {
|
|
470
|
-
if (!isAbsolute(value)) throw new Error(`${label} must be absolute: ${value}`)
|
|
471
|
-
}
|
|
472
|
-
const home = resolve(options.home)
|
|
473
|
-
const agents = resolveLaunchAgents(options.manifest, options.platform, options.device)
|
|
474
|
-
const previous =
|
|
475
|
-
options.previousManifest === null ? {} : resolveLabels(options.previousManifest, options.platform, options.device)
|
|
476
|
-
|
|
477
|
-
// Fail before the first mutation if any declared job entry is missing.
|
|
478
|
-
for (const spec of Object.values(agents)) {
|
|
479
|
-
if (readIfFile(join(options.repoRoot, spec.source, spec.entry)) === null) {
|
|
480
|
-
throw new Error(`launch agent job entry missing from checkout: ${join(spec.source, spec.entry)}`)
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
const homeStat = statSync(home)
|
|
485
|
-
const uid = homeStat.uid
|
|
486
|
-
const owner = typeof process.getuid === 'function' && process.getuid() === 0 ? { uid, gid: homeStat.gid } : null
|
|
487
|
-
const lines: string[] = []
|
|
488
|
-
|
|
489
|
-
const currentLabels = new Set(Object.values(agents).map((spec) => spec.label))
|
|
490
|
-
for (const [id, label] of Object.entries(previous)) {
|
|
491
|
-
if (currentLabels.has(label)) continue
|
|
492
|
-
const relativePlist = `${LAUNCH_AGENTS_DIR}/${label}.plist`
|
|
493
|
-
options.launchctl.bootout(uid, label)
|
|
494
|
-
if (options.launchctl.isLoaded(uid, label)) {
|
|
495
|
-
throw new Error(`launch agent ${label} is still loaded after bootout; refusing to remove it`)
|
|
496
|
-
}
|
|
497
|
-
rmSync(join(home, relativePlist), { force: true })
|
|
498
|
-
if (!(id in agents)) {
|
|
499
|
-
rmSync(installedProgramPath(id, options.libexecDir), { force: true })
|
|
500
|
-
rmSync(sourceHashPath(options.stateDir, id), { force: true })
|
|
501
|
-
}
|
|
502
|
-
lines.push(`launch-agent: removed ${label} (deleted from manifest)`)
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
// Only remove entries this component owns: currently declared ids, or ids
|
|
506
|
-
// it previously built (a `.source-hash` marker under stateDir). The dir is
|
|
507
|
-
// shared with other managers (e.g. repo-dir targets), so an entry with
|
|
508
|
-
// neither is left alone even if it looks stale.
|
|
509
|
-
if (existsSync(options.libexecDir)) {
|
|
510
|
-
const expected = new Set(Object.keys(agents))
|
|
511
|
-
const owned = ownedProgramIds(options.stateDir, expected)
|
|
512
|
-
for (const name of readdirSync(options.libexecDir)) {
|
|
513
|
-
if (expected.has(name) || !owned.has(name)) continue
|
|
514
|
-
rmSync(join(options.libexecDir, name), { recursive: true, force: true })
|
|
515
|
-
lines.push(`launch-agent: removed stale ${join(options.libexecDir, name)}`)
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
for (const [id, spec] of Object.entries(agents)) {
|
|
520
|
-
const relativePlist = `${LAUNCH_AGENTS_DIR}/${spec.label}.plist`
|
|
521
|
-
const plistPath = join(home, relativePlist)
|
|
522
|
-
const programPath = installedProgramPath(id, options.libexecDir)
|
|
523
|
-
const plistContent = renderLaunchAgentPlist(spec, { home, programPath })
|
|
524
|
-
|
|
525
|
-
let changed = false
|
|
526
|
-
const hash = needsBuild(id, spec, options)
|
|
527
|
-
if (hash !== null) {
|
|
528
|
-
// Build to a staged path so a failed build never clobbers a working binary.
|
|
529
|
-
const staged = `${programPath}.staged`
|
|
530
|
-
mkdirSync(options.libexecDir, { recursive: true, mode: 0o755 })
|
|
531
|
-
rmSync(staged, { force: true })
|
|
532
|
-
try {
|
|
533
|
-
options.builder.build(join(options.repoRoot, spec.source, spec.entry), staged, spec.label)
|
|
534
|
-
chmodSync(staged, 0o755)
|
|
535
|
-
} catch (cause) {
|
|
536
|
-
rmSync(staged, { force: true })
|
|
537
|
-
throw cause
|
|
538
|
-
}
|
|
539
|
-
renameSync(staged, programPath)
|
|
540
|
-
atomicWrite(sourceHashPath(options.stateDir, id), `${hash}\n`, 0o644, null)
|
|
541
|
-
changed = true
|
|
542
|
-
lines.push(`launch-agent: built ${programPath} (identifier ${spec.label})`)
|
|
543
|
-
}
|
|
544
|
-
const existingPlist = readIfFile(plistPath)
|
|
545
|
-
if (existingPlist !== plistContent) {
|
|
546
|
-
ensureOwnedDir(join(home, 'Library'), owner)
|
|
547
|
-
ensureOwnedDir(join(home, LAUNCH_AGENTS_DIR), owner)
|
|
548
|
-
atomicWrite(plistPath, plistContent, 0o644, owner)
|
|
549
|
-
changed = true
|
|
550
|
-
lines.push(`launch-agent: wrote ${plistPath}`)
|
|
551
|
-
}
|
|
552
|
-
for (const legacy of spec.replaces) {
|
|
553
|
-
if (existsSync(join(home, legacy))) {
|
|
554
|
-
rmSync(join(home, legacy), { recursive: true, force: true })
|
|
555
|
-
lines.push(`launch-agent: removed retired ${join(home, legacy)}`)
|
|
556
|
-
changed = true
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
const denial = lastRunDenial(spec, home)
|
|
560
|
-
if (denial !== null) lines.push(denial)
|
|
561
|
-
let logDir = home
|
|
562
|
-
for (const part of dirname(spec.log).split('/')) {
|
|
563
|
-
logDir = join(logDir, part)
|
|
564
|
-
ensureOwnedDir(logDir, owner)
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
if (!options.launchctl.hasGuiDomain(uid)) {
|
|
568
|
-
lines.push(`launch-agent: ${spec.label} installed; no GUI session for uid ${uid}, launchd loads it at next login`)
|
|
569
|
-
continue
|
|
570
|
-
}
|
|
571
|
-
if (!changed && options.launchctl.isLoaded(uid, spec.label)) {
|
|
572
|
-
lines.push(`launch-agent: already up to date: ${spec.label}`)
|
|
573
|
-
continue
|
|
574
|
-
}
|
|
575
|
-
options.launchctl.bootout(uid, spec.label)
|
|
576
|
-
const loaded = options.launchctl.bootstrap(uid, plistPath)
|
|
577
|
-
if (!loaded.ok) throw new Error(`launchctl bootstrap gui/${uid} ${plistPath} failed: ${loaded.detail}`)
|
|
578
|
-
lines.push(`launch-agent: loaded gui/${uid}/${spec.label}`)
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
return { lines }
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
function usage(): never {
|
|
585
|
-
throw new Error(
|
|
586
|
-
'usage: launch-agents.ts --mode plan|apply --manifest FILE [--previous-manifest FILE] --platform mac|linux ' +
|
|
587
|
-
'--device NAME --home ABSOLUTE_PATH --state-dir ABSOLUTE_PATH [--libexec-dir ABSOLUTE_PATH]',
|
|
588
|
-
)
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
export function main(argv: readonly string[]): void {
|
|
592
|
-
const args = new Map<string, string>()
|
|
593
|
-
for (let index = 0; index < argv.length; index += 2) {
|
|
594
|
-
const key = argv[index]
|
|
595
|
-
const value = argv[index + 1]
|
|
596
|
-
if (key === undefined || !key.startsWith('--') || value === undefined) usage()
|
|
597
|
-
args.set(key.slice(2), value)
|
|
598
|
-
}
|
|
599
|
-
const mode = args.get('mode')
|
|
600
|
-
const manifestPath = args.get('manifest')
|
|
601
|
-
const platform = args.get('platform')
|
|
602
|
-
const device = args.get('device')
|
|
603
|
-
const home = args.get('home')
|
|
604
|
-
const stateDir = args.get('state-dir')
|
|
605
|
-
if (
|
|
606
|
-
(mode !== 'plan' && mode !== 'apply') ||
|
|
607
|
-
manifestPath === undefined ||
|
|
608
|
-
(platform !== 'mac' && platform !== 'linux') ||
|
|
609
|
-
device === undefined ||
|
|
610
|
-
home === undefined ||
|
|
611
|
-
stateDir === undefined
|
|
612
|
-
) {
|
|
613
|
-
usage()
|
|
614
|
-
}
|
|
615
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
616
|
-
const previousPath = args.get('previous-manifest')
|
|
617
|
-
const previousManifest = previousPath === undefined ? null : JSON.parse(readFileSync(previousPath, 'utf8'))
|
|
618
|
-
const libexecDir = args.get('libexec-dir') ?? DEFAULT_LIBEXEC_DIR
|
|
619
|
-
const repoRoot = resolve(import.meta.dir, '..', '..', '..')
|
|
620
|
-
|
|
621
|
-
if (mode === 'plan') {
|
|
622
|
-
for (const line of planLaunchAgents({ manifest, previousManifest, platform, device, home, repoRoot, libexecDir, stateDir })) {
|
|
623
|
-
console.log(line)
|
|
624
|
-
}
|
|
625
|
-
return
|
|
626
|
-
}
|
|
627
|
-
const result = applyLaunchAgents({
|
|
628
|
-
manifest,
|
|
629
|
-
previousManifest,
|
|
630
|
-
platform,
|
|
631
|
-
device,
|
|
632
|
-
home,
|
|
633
|
-
repoRoot,
|
|
634
|
-
libexecDir,
|
|
635
|
-
stateDir,
|
|
636
|
-
launchctl: systemLaunchctl,
|
|
637
|
-
builder: systemProgramBuilder(process.execPath, join(repoRoot, 'bin/lib/bun-compile-build.sh')),
|
|
638
|
-
})
|
|
639
|
-
for (const line of result.lines) console.log(line)
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
if (import.meta.main) {
|
|
643
|
-
try {
|
|
644
|
-
main(process.argv.slice(2))
|
|
645
|
-
} catch (cause) {
|
|
646
|
-
console.error(`launch-agents: ${cause instanceof Error ? cause.message : String(cause)}`)
|
|
647
|
-
process.exitCode = 1
|
|
648
|
-
}
|
|
649
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# Invariants
|
|
2
|
-
|
|
3
|
-
The things this repository must never regress. These apply to every repository
|
|
4
|
-
adl manages; add this repository's own below, one bullet each, stating the rule
|
|
5
|
-
and how it is verified.
|
|
6
|
-
|
|
7
|
-
- The compliance gates enabled in `adl-config.toml` pass before every commit
|
|
8
|
-
(verified by `adl repo controls` pre-commit and CI). A gate is switched off by
|
|
9
|
-
removing it from `[controls.compliance] enabled`, which is the owner's decision.
|
|
10
|
-
- Protected paths (`adl-config.toml`, `.workflows/`, the agent surface folders,
|
|
11
|
-
`workflows/` and the declared compliance register folder) are landed only by the
|
|
12
|
-
owner. An agent leaves them uncommitted and asks (verified by the pre-commit
|
|
13
|
-
integrity gate).
|
|
14
|
-
- Ratchets only shrink. A register names the specific files granted, and a grant
|
|
15
|
-
is removed once its file is clean, never added to (verified by the gate that reads
|
|
16
|
-
the register).
|
|
17
|
-
- No multi-line comments in code. The explanation lives in `docs/`, and the code
|
|
18
|
-
keeps at most a one-line pointer (verified by `repository.no-multiline-comments`).
|
|
19
|
-
- The standard trees `docs`, `plans`, `workflows` and `compliance` are declared in
|
|
20
|
-
`[controls.compliance.trees]`, and layout rules follow those declarations
|
|
21
|
-
(verified by the layout and docs gates).
|