@crvouga/dev-fleet 0.8.0 → 0.9.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.
Files changed (204) hide show
  1. package/README.md +65 -9
  2. package/agents/audit.md +18 -18
  3. package/ansible/agents/agents.yml +134 -0
  4. package/ansible/agents/templates/antigravity.settings.json.j2 +3 -0
  5. package/ansible/agents/templates/claude.settings.json.j2 +3 -0
  6. package/ansible/agents/templates/codex.config.toml.j2 +11 -0
  7. package/ansible/agents/templates/gemini.settings.json.j2 +14 -0
  8. package/ansible/agents/templates/grok.config.toml.j2 +19 -0
  9. package/ansible/agents/templates/opencode.json.j2 +24 -0
  10. package/ansible/agents/templates/receipt.json.j2 +10 -0
  11. package/ansible/ansible.cfg +14 -0
  12. package/ansible/github/git.yml +82 -0
  13. package/ansible/macos/manager-privilege.yml +55 -9
  14. package/ansible/macos/manager.yml +154 -7
  15. package/ansible/macos/policy.json +13 -0
  16. package/ansible/macos/t3-policy.yml +5 -3
  17. package/ansible/macos/vault-renew.yml +85 -0
  18. package/ansible/monitoring/agent-darwin.yml +3 -0
  19. package/ansible/monitoring/agent.yml +2 -0
  20. package/ansible/monitoring/hub-darwin.yml +3 -0
  21. package/ansible/monitoring/hub-route-darwin.yml +1 -0
  22. package/ansible/net/remote-probe.sh +4 -1
  23. package/ansible/platforms.json +2 -2
  24. package/ansible/pyproject.toml +5 -0
  25. package/ansible/ubuntu/baseline.yml +39 -13
  26. package/ansible/ubuntu/disk-gc.py +6 -2
  27. package/ansible/ubuntu/firewall.nft.j2 +50 -0
  28. package/ansible/ubuntu/policy.json +125 -6
  29. package/ansible/ubuntu/reboot.yml +89 -0
  30. package/ansible/ubuntu/recovery.yml +13 -3
  31. package/ansible/ubuntu/t3/policy.json +3 -0
  32. package/ansible/ubuntu/t3-desktop.yml +23 -2
  33. package/ansible/ubuntu/t3-headless.yml +128 -0
  34. package/ansible/ubuntu/t3-health.sh +24 -10
  35. package/ansible/ubuntu/t3-idle.py +37 -2
  36. package/ansible/ubuntu/t3-policy.yml +4 -1
  37. package/ansible/ubuntu/t3-route.yml +1 -0
  38. package/ansible/ubuntu/tailscale.yml +54 -8
  39. package/ansible/ubuntu/tasks/browser.yml +3 -0
  40. package/ansible/ubuntu/tasks/containers-off.yml +43 -0
  41. package/ansible/ubuntu/tasks/docker.yml +53 -6
  42. package/ansible/ubuntu/tasks/dpkg.yml +4 -0
  43. package/ansible/ubuntu/tasks/features.yml +22 -0
  44. package/ansible/ubuntu/tasks/firewall.yml +172 -0
  45. package/ansible/ubuntu/tasks/install.yml +2 -1
  46. package/ansible/ubuntu/tasks/packages.yml +4 -2
  47. package/ansible/ubuntu/tasks/release.yml +6 -0
  48. package/ansible/ubuntu/tasks/resources.yml +6 -4
  49. package/ansible/ubuntu/tasks/services.yml +2 -2
  50. package/ansible/ubuntu/tasks/sizing.yml +49 -0
  51. package/ansible/ubuntu/tasks/snapshot.yml +14 -3
  52. package/ansible/ubuntu/tasks/ssh.yml +61 -4
  53. package/ansible/ubuntu/tasks/t3-restart.yml +3 -1
  54. package/ansible/ubuntu/tasks/user.yml +1 -0
  55. package/ansible/ubuntu/worker-account.yml +117 -0
  56. package/ansible/uv.lock +265 -0
  57. package/bin/devfleet.mjs +42 -4
  58. package/bootstrap/fleet-remote-bootstrap.sh +91 -25
  59. package/bootstrap/global-config.ts +29 -3
  60. package/bootstrap/package-smoke.ts +127 -0
  61. package/bootstrap/pins.ts +189 -0
  62. package/bootstrap/release-notes.ts +88 -0
  63. package/bootstrap/release-version.ts +41 -7
  64. package/bootstrap/worker-features.ts +114 -0
  65. package/bootstrap/worker-seed.yaml +7 -3
  66. package/bootstrap/worker-tools.ts +39 -9
  67. package/cliproxy/README.md +1 -1
  68. package/cliproxy/docs/health-workflow.example.yml +20 -143
  69. package/docs/authentication.md +19 -11
  70. package/docs/consumer-setup.md +42 -27
  71. package/docs/convergence.md +67 -0
  72. package/docs/declared.md +61 -34
  73. package/docs/machine-setup.md +73 -7
  74. package/docs/manager-setup.md +10 -3
  75. package/docs/operations.md +64 -9
  76. package/docs/releasing.md +49 -1
  77. package/docs/runbooks.md +98 -7
  78. package/docs/state.md +23 -11
  79. package/docs/support-matrix.md +40 -4
  80. package/docs/topology.md +67 -6
  81. package/docs/ubuntu-sync.md +78 -28
  82. package/docs/worker-contract.md +122 -19
  83. package/docs/worker-requirements.md +107 -0
  84. package/examples/fleet.json +1 -0
  85. package/examples/workers-from-infrastructure/main.tf +132 -0
  86. package/examples/workers-from-infrastructure/worker-records.json +14 -0
  87. package/llms.txt +3 -1
  88. package/mise.lock +29 -0
  89. package/mise.toml +5 -3
  90. package/package.json +2 -2
  91. package/schema/check-ids.json +1967 -170
  92. package/schema/check-ids.schema.json +34 -0
  93. package/schema/fleet.schema.json +327 -8
  94. package/schema/output/check.schema.json +1 -1
  95. package/schema/output/fleet-state-definition.schema.json +14 -0
  96. package/schema/output/fleet-sync-schedule.schema.json +26 -0
  97. package/schema/output/fleet-sync.schema.json +18 -0
  98. package/schema/output/report.schema.json +3 -2
  99. package/schema/worker-record.schema.json +76 -0
  100. package/src/agents/converge.ts +140 -24
  101. package/src/agents/files.ts +2 -32
  102. package/src/agents/native-verify.ts +34 -3
  103. package/src/agents/secrets.ts +39 -4
  104. package/src/check-owners.ts +61 -2
  105. package/src/check-registry.ts +630 -0
  106. package/src/cli/agent-guide.ts +3 -3
  107. package/src/cli/cli.ts +4 -0
  108. package/src/cli/fleet-manifest.ts +9 -3
  109. package/src/cli/fleet-node-lifecycle.ts +313 -64
  110. package/src/cli/fleet-node-park.ts +238 -0
  111. package/src/cli/fleet-node-seed.ts +51 -35
  112. package/src/cli/fleet-state.ts +2 -1
  113. package/src/cli/fleet-sync-checkout.ts +7 -0
  114. package/src/cli/fleet-sync-enrollment.ts +193 -32
  115. package/src/cli/fleet-sync-forward.ts +4 -0
  116. package/src/cli/fleet-sync-manager.ts +103 -33
  117. package/src/cli/fleet-sync-output.ts +31 -11
  118. package/src/cli/fleet-sync-plan.ts +65 -9
  119. package/src/cli/fleet-sync-records.ts +113 -0
  120. package/src/cli/fleet-sync-remote.ts +151 -9
  121. package/src/cli/fleet-sync-schedule.ts +14 -4
  122. package/src/cli/fleet-sync-state.ts +28 -16
  123. package/src/cli/fleet-sync-vault.ts +77 -0
  124. package/src/cli/fleet-sync-worker.ts +214 -0
  125. package/src/cli/fleet-sync.ts +150 -19
  126. package/src/cli/fleet-validate.ts +59 -21
  127. package/src/cli/fleet-watch.ts +7 -1
  128. package/src/cli/fleet.ts +87 -5
  129. package/src/cli/managed-health.ts +25 -1
  130. package/src/cli/register-fleet.ts +115 -4
  131. package/src/cli/registry.ts +14 -0
  132. package/src/cli/tailnet.ts +43 -10
  133. package/src/cli/ui-data.ts +3 -1
  134. package/src/cli/worker-usage-report.ts +63 -0
  135. package/src/clock.ts +42 -0
  136. package/src/definition.ts +332 -9
  137. package/src/diagnostics.ts +5 -2
  138. package/src/exposure.ts +102 -0
  139. package/src/fleet-config.ts +3 -1
  140. package/src/git-identity.ts +76 -3
  141. package/src/ide/ide.ts +5 -0
  142. package/src/ide/t3.ts +12 -2
  143. package/src/input-hashes.ts +6 -0
  144. package/src/machine/machine-class.ts +59 -0
  145. package/src/machine/platform.ts +90 -11
  146. package/src/machine/state.ts +37 -15
  147. package/src/machine/support-matrix.ts +11 -9
  148. package/src/machine-health.ts +28 -5
  149. package/src/manager-health.ts +148 -2
  150. package/src/manager-sync.ts +65 -4
  151. package/src/monitoring/beszel.ts +8 -8
  152. package/src/monitoring/enroll.ts +8 -1
  153. package/src/monitoring/hub-access.ts +10 -4
  154. package/src/monitoring/systems.ts +138 -0
  155. package/src/network-definition.ts +13 -4
  156. package/src/node-converge-linux.ts +136 -27
  157. package/src/node-converge.ts +13 -3
  158. package/src/platform/ansible-receipt.ts +17 -2
  159. package/src/platform/ansible.ts +40 -4
  160. package/src/platform/manager-host.ts +25 -8
  161. package/src/platform/resource-verify.ts +18 -7
  162. package/src/platform/t3-policy.ts +24 -10
  163. package/src/platform/t3-service.ts +109 -34
  164. package/src/platform/ubuntu-release.ts +24 -0
  165. package/src/platform/ubuntu-verify.ts +31 -3
  166. package/src/platform/ubuntu-worker.ts +58 -0
  167. package/src/platform/worker-platform.ts +72 -0
  168. package/src/protocol.ts +1 -1
  169. package/src/public-reachability.ts +186 -0
  170. package/src/remote-bootstrap.ts +50 -12
  171. package/src/remote-operation.ts +18 -4
  172. package/src/repair-plan.ts +23 -12
  173. package/src/report.ts +2 -1
  174. package/src/schedule-check.ts +21 -2
  175. package/src/secrets.ts +4 -0
  176. package/src/service-auth.ts +55 -48
  177. package/src/ssh-connection.ts +141 -0
  178. package/src/ssh.ts +63 -24
  179. package/src/state/store.ts +4 -2
  180. package/src/state/sync.ts +35 -6
  181. package/src/state/workers.ts +140 -0
  182. package/src/t3/pairing.ts +59 -0
  183. package/src/t3/peer-link.ts +333 -0
  184. package/src/t3/version.ts +1 -1
  185. package/src/tailnet-cleanup.ts +185 -0
  186. package/src/tailnet-control.ts +346 -0
  187. package/src/tailscale.ts +1 -1
  188. package/src/toolchain-checks.ts +11 -1
  189. package/src/types.ts +4 -2
  190. package/src/worker-account.ts +63 -0
  191. package/src/worker-activity.ts +303 -0
  192. package/src/worker-capacity.ts +31 -0
  193. package/src/worker-exposure.ts +388 -0
  194. package/src/worker-features.ts +201 -0
  195. package/src/worker-host-checks.ts +71 -20
  196. package/src/worker-manifest.ts +27 -4
  197. package/src/worker-parking.ts +70 -0
  198. package/src/worker-policy.ts +17 -1
  199. package/src/worker-readiness.ts +15 -3
  200. package/src/worker-reboot.ts +129 -0
  201. package/src/worker-records.ts +305 -0
  202. package/src/worker-seed.ts +16 -11
  203. package/src/worker-sizing.ts +76 -0
  204. package/src/cli/schedule-logs.ts +0 -36
@@ -2,6 +2,7 @@
2
2
  - name: Inspect effective SSH configuration before changes
3
3
  ansible.builtin.command: /usr/sbin/sshd -T
4
4
  changed_when: false
5
+ check_mode: false
5
6
  register: effective_ssh
6
7
  - name: Inspect any existing managed SSH fragment
7
8
  ansible.builtin.stat:
@@ -13,12 +14,27 @@
13
14
  when: ssh_owned_file.stat.exists
14
15
  register: ssh_previous
15
16
  no_log: true
17
+ # Authentication is hardened in a fragment of its own that sorts before the
18
+ # image's (cloud-init writes 50-cloud-init.conf): sshd keeps the first value it
19
+ # reads. Listeners stay as they are; the firewall (firewall.yml) decides which
20
+ # interfaces reach them.
21
+ - name: Inspect any existing managed SSH hardening fragment
22
+ ansible.builtin.stat:
23
+ path: /etc/ssh/sshd_config.d/10-personal-fleet-exposure.conf
24
+ register: ssh_hardening_file
25
+ - name: Read previous hardening fragment for validation rollback
26
+ ansible.builtin.slurp:
27
+ src: /etc/ssh/sshd_config.d/10-personal-fleet-exposure.conf
28
+ when: ssh_hardening_file.stat.exists
29
+ register: ssh_hardening_previous
30
+ no_log: true
16
31
  - name: Refuse to overwrite unmanaged SSH configuration
17
32
  ansible.builtin.assert:
18
33
  that:
19
34
  - not ssh_owned_file.stat.exists or (ssh_previous.content | b64decode).startswith('# Managed by personal Fleet')
35
+ - not ssh_hardening_file.stat.exists or (ssh_hardening_previous.content | b64decode).startswith('# Managed by personal Fleet')
20
36
  fail_msg: SSH_UNMANAGED_CONFIG_CONFLICT; review the existing fragment before adoption.
21
- - name: Stage and validate forwarding policy while preserving listeners and authentication
37
+ - name: Stage and validate forwarding and authentication policy while preserving listeners
22
38
  block:
23
39
  - name: Write forwarding-only owned fragment
24
40
  ansible.builtin.copy:
@@ -28,14 +44,31 @@
28
44
  mode: '0644'
29
45
  backup: true
30
46
  content: |
31
- # Managed by personal Fleet; listeners and authentication remain explicit policy.
47
+ # Managed by personal Fleet; forwarding and keep-alive. Authentication: 10-personal-fleet-exposure.conf.
32
48
  AllowTcpForwarding yes
33
49
  ClientAliveInterval 30
34
50
  ClientAliveCountMax 3
35
51
  register: ssh_fragment
52
+ - name: Write keys-only authentication fragment
53
+ ansible.builtin.copy:
54
+ dest: /etc/ssh/sshd_config.d/10-personal-fleet-exposure.conf
55
+ owner: root
56
+ group: root
57
+ mode: '0644'
58
+ backup: true
59
+ content: |
60
+ # Managed by personal Fleet; keys only and no root login (worker exposure policy).
61
+ PubkeyAuthentication yes
62
+ PasswordAuthentication no
63
+ KbdInteractiveAuthentication no
64
+ PermitEmptyPasswords no
65
+ PermitRootLogin no
66
+ register: ssh_hardening
36
67
  - name: Validate complete SSH configuration
37
68
  ansible.builtin.command: /usr/sbin/sshd -t
38
69
  changed_when: false
70
+ # Verifies a change a dry run does not make.
71
+ when: not ansible_check_mode
39
72
  rescue:
40
73
  - name: Restore previous fragment after validation failure
41
74
  ansible.builtin.copy:
@@ -51,6 +84,20 @@
51
84
  path: /etc/ssh/sshd_config.d/60-personal-fleet.conf
52
85
  state: absent
53
86
  when: not ssh_owned_file.stat.exists
87
+ - name: Restore previous hardening fragment after validation failure
88
+ ansible.builtin.copy:
89
+ content: '{{ ssh_hardening_previous.content | b64decode }}'
90
+ dest: /etc/ssh/sshd_config.d/10-personal-fleet-exposure.conf
91
+ owner: root
92
+ group: root
93
+ mode: '0644'
94
+ when: ssh_hardening_file.stat.exists
95
+ no_log: true
96
+ - name: Remove rejected new hardening fragment
97
+ ansible.builtin.file:
98
+ path: /etc/ssh/sshd_config.d/10-personal-fleet-exposure.conf
99
+ state: absent
100
+ when: not ssh_hardening_file.stat.exists
54
101
  - name: Report validation failure without reloading sshd
55
102
  ansible.builtin.fail:
56
103
  msg: SSH_CONFIG_REJECTED; previous configuration restored, active service untouched.
@@ -59,13 +106,23 @@
59
106
  name: ssh.service
60
107
  enabled: true
61
108
  state: started
62
- - name: Reload validated forwarding policy without terminating sessions
109
+ - name: Reload validated forwarding and authentication policy without terminating sessions
63
110
  ansible.builtin.systemd_service:
64
111
  name: ssh.service
65
112
  state: reloaded
66
- when: ssh_fragment.changed
113
+ when: ssh_fragment.changed or ssh_hardening.changed
67
114
  - name: Verify effective forwarding after reload
68
115
  ansible.builtin.command: /usr/sbin/sshd -T
69
116
  changed_when: false
117
+ # Verifies a change a dry run does not make.
118
+ when: not ansible_check_mode
70
119
  register: ssh_verified
71
120
  failed_when: "'allowtcpforwarding yes' not in ssh_verified.stdout"
121
+ - name: Require keys-only authentication without root login after reload
122
+ ansible.builtin.assert:
123
+ that:
124
+ - "'passwordauthentication no' in ssh_verified.stdout_lines"
125
+ - "'kbdinteractiveauthentication no' in ssh_verified.stdout_lines"
126
+ - "'permitrootlogin no' in ssh_verified.stdout_lines"
127
+ fail_msg: SSH_HARDENING_OVERRIDDEN; an earlier sshd_config fragment or Match block keeps password or root login; remove it, then rerun fleet-sync.
128
+ when: not ansible_check_mode
@@ -3,6 +3,7 @@
3
3
  # running turn. A busy T3 keeps its turns; the restart is recorded as pending
4
4
  # (with the time it was first deferred) and personal-worker-recovery applies it
5
5
  # at the first idle check (t3-health.sh). t3_restart_busy restarts it anyway.
6
+ # `t3_unit` names the unit of a headless worker (t3-headless.yml).
6
7
  - name: Ask T3 whether an agent turn is running
7
8
  become: true
8
9
  become_user: '{{ development_user }}'
@@ -16,6 +17,7 @@
16
17
  - '{{ development_home }}/.t3/userdata/statev2.sqlite'
17
18
  register: t3_idle
18
19
  changed_when: false
20
+ check_mode: false
19
21
  failed_when: false
20
22
  - name: Restart T3 while it runs no agent turn
21
23
  become: true
@@ -24,7 +26,7 @@
24
26
  XDG_RUNTIME_DIR: '/run/user/{{ development_uid }}'
25
27
  ansible.builtin.systemd_service:
26
28
  scope: user
27
- name: t3code-desktop.service
29
+ name: "{{ t3_unit | default('t3code-desktop.service') }}"
28
30
  state: restarted
29
31
  when: t3_idle is not skipped and (t3_idle.rc == 0 or t3_restart_busy | default(false) | bool)
30
32
  - name: Record the restart as pending while agent turns run
@@ -97,6 +97,7 @@
97
97
  }
98
98
  when: gnome_schema.stat.exists
99
99
  changed_when: false
100
+ check_mode: false
100
101
  register: gnome_settings
101
102
  - name: Repair only changed designated-user settings
102
103
  become: true
@@ -0,0 +1,117 @@
1
+ ---
2
+ # The declared worker account on a machine whose image lets only root in
3
+ # (many rented servers). fleet-sync applies it from the manager, once, over
4
+ # the root login: it creates workers[].network.user with the key root was
5
+ # reached with and the fleet's sudo rule (the path and marker
6
+ # authorize-sudo.sh writes), then continues as that account. The developer
7
+ # toolchain never runs as root. Turning root login off afterwards is the SSH
8
+ # policy's. Variables: target (the SSH alias, or the declared tailnet name),
9
+ # login (root, or empty for the alias's own user, which is root), user (the
10
+ # declared account) and ssh_options (the SSH options every session of the
11
+ # manager takes, src/ssh-connection.ts, with the pinned host key).
12
+ - name: Address the worker over its root login
13
+ hosts: localhost
14
+ connection: local
15
+ gather_facts: false
16
+ tasks:
17
+ - name: Check the request
18
+ ansible.builtin.assert:
19
+ that:
20
+ - "target is match('^[a-zA-Z0-9][a-zA-Z0-9._-]{0,252}$')"
21
+ - ssh_options is string and ssh_options is match('^-o ')
22
+ - "user is match('^[a-z_][a-z0-9_-]{0,31}$')"
23
+ - user != 'root'
24
+ - login | default('') in ['', 'root']
25
+ fail_msg: WORKER_ACCOUNT_UNDECLARED; declare workers[].network.user as a non-root account.
26
+ - name: Address the worker by its alias and its own user, with strict host keys
27
+ ansible.builtin.add_host:
28
+ name: '{{ target }}'
29
+ groups: [fleet_account]
30
+ ansible_python_interpreter: /usr/bin/python3
31
+ ansible_ssh_args: -o ControlMaster=no -o ControlPath=none
32
+ ansible_ssh_common_args: '{{ ssh_options }}'
33
+ account_user: '{{ user }}'
34
+ changed_when: false
35
+ when: login | default('') == ''
36
+ - name: Address the worker by its alias as root, with strict host keys
37
+ ansible.builtin.add_host:
38
+ name: '{{ target }}'
39
+ groups: [fleet_account]
40
+ ansible_python_interpreter: /usr/bin/python3
41
+ ansible_ssh_args: -o ControlMaster=no -o ControlPath=none
42
+ ansible_ssh_common_args: '{{ ssh_options }}'
43
+ ansible_user: root
44
+ account_user: '{{ user }}'
45
+ changed_when: false
46
+ when: login | default('') == 'root'
47
+
48
+ - name: Create the declared worker account
49
+ hosts: fleet_account
50
+ gather_facts: false
51
+ serial: 1
52
+ tasks:
53
+ - name: Ask which account the login is
54
+ ansible.builtin.command:
55
+ argv: [id, -u]
56
+ register: login_uid
57
+ changed_when: false
58
+ check_mode: false
59
+ - name: Require the root login
60
+ ansible.builtin.assert:
61
+ that: login_uid.stdout == '0'
62
+ fail_msg: WORKER_ACCOUNT_ROOT_REQUIRED; only a root login creates the declared account.
63
+ - name: Create the account, its home and its sudo group membership
64
+ ansible.builtin.user:
65
+ name: '{{ account_user }}'
66
+ home: '/home/{{ account_user }}'
67
+ create_home: true
68
+ shell: /bin/bash
69
+ groups: [sudo]
70
+ append: true
71
+ password_lock: true
72
+ - name: Create the account's SSH directory
73
+ ansible.builtin.file:
74
+ path: '/home/{{ account_user }}/.ssh'
75
+ state: directory
76
+ owner: '{{ account_user }}'
77
+ group: '{{ account_user }}'
78
+ mode: '0700'
79
+ # The key the manager reached root with; an account that already holds
80
+ # keys of its own keeps them.
81
+ - name: Authorise the key root was reached with
82
+ ansible.builtin.copy:
83
+ src: /root/.ssh/authorized_keys
84
+ remote_src: true
85
+ dest: '/home/{{ account_user }}/.ssh/authorized_keys'
86
+ owner: '{{ account_user }}'
87
+ group: '{{ account_user }}'
88
+ mode: '0600'
89
+ force: false
90
+ - name: Inspect an existing sudo rule of the account
91
+ ansible.builtin.stat:
92
+ path: '/etc/sudoers.d/personal-fleet-{{ account_user }}'
93
+ register: sudo_rule
94
+ - name: Read the ownership marker of the existing rule
95
+ ansible.builtin.command:
96
+ argv:
97
+ - grep
98
+ - -qxF
99
+ - '# Managed by personal Fleet: designated development user'
100
+ - '/etc/sudoers.d/personal-fleet-{{ account_user }}'
101
+ register: sudo_marker
102
+ changed_when: false
103
+ check_mode: false
104
+ failed_when: false
105
+ when: sudo_rule.stat.exists
106
+ - name: Never take over a rule the fleet did not write
107
+ ansible.builtin.assert:
108
+ that: not sudo_rule.stat.exists or sudo_marker.rc == 0
109
+ fail_msg: SUDOERS_OWNERSHIP_CONFLICT; preserve and review the existing fragment before adoption.
110
+ - name: Grant the account the fleet's sudo rule
111
+ ansible.builtin.copy:
112
+ dest: '/etc/sudoers.d/personal-fleet-{{ account_user }}'
113
+ content: "# Managed by personal Fleet: designated development user\n{{ account_user }} ALL=(ALL:ALL) NOPASSWD: ALL\n"
114
+ owner: root
115
+ group: root
116
+ mode: '0440'
117
+ validate: visudo -cf %s
package/ansible/uv.lock CHANGED
@@ -2,6 +2,22 @@ version = 1
2
2
  revision = 5
3
3
  requires-python = "==3.13.16"
4
4
 
5
+ [[package]]
6
+ name = "ansible-compat"
7
+ version = "26.11.0"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ dependencies = [
10
+ { name = "ansible-core" },
11
+ { name = "jsonschema" },
12
+ { name = "packaging" },
13
+ { name = "pyyaml" },
14
+ { name = "subprocess-tee" },
15
+ ]
16
+ sdist = { url = "https://files.pythonhosted.org/packages/e8/b3/64652d98718d5a597a15c2eb95e55fd6935b1c4eeee316370e5a498a2a6e/ansible_compat-26.11.0.tar.gz", hash = "sha256:35cbe829e0b5a9acb85d34b8d5da2eb45acaf767f6651f90e0ab2f3a96a060bb", size = 295998, upload-time = "2026-11-05T08:41:46.716Z" }
17
+ wheels = [
18
+ { url = "https://files.pythonhosted.org/packages/88/3e/696ba32885c888867214baba910cc07c13aa410884a04ff0892f9b744e3c/ansible_compat-26.11.0-py3-none-any.whl", hash = "sha256:c06a486b39ced440f9f6fb9a99583c73d9239c94ece822a27ea2242173462dda", size = 29027, upload-time = "2026-11-05T08:41:45.462Z" },
19
+ ]
20
+
5
21
  [[package]]
6
22
  name = "ansible-core"
7
23
  version = "2.19.9"
@@ -18,6 +34,73 @@ wheels = [
18
34
  { url = "https://files.pythonhosted.org/packages/3d/b9/f2c07e6cdba6a5882342d666ceea4eeeb6fea20ec325fac68c27bfcea722/ansible_core-2.19.9-py3-none-any.whl", hash = "sha256:18de80e3ea5a89d2ea84e4d4a3c6d7c121ea62fc3af2877ba083784e3d90b419", size = 2416269, upload-time = "2026-04-21T00:47:06.777Z" },
19
35
  ]
20
36
 
37
+ [[package]]
38
+ name = "ansible-lint"
39
+ version = "26.11.0"
40
+ source = { registry = "https://pypi.org/simple" }
41
+ dependencies = [
42
+ { name = "ansible-compat" },
43
+ { name = "ansible-core" },
44
+ { name = "black" },
45
+ { name = "cffi" },
46
+ { name = "cryptography" },
47
+ { name = "distro" },
48
+ { name = "filelock" },
49
+ { name = "jsonschema" },
50
+ { name = "packaging" },
51
+ { name = "pathspec" },
52
+ { name = "pyyaml" },
53
+ { name = "referencing" },
54
+ { name = "ruamel-yaml" },
55
+ { name = "subprocess-tee" },
56
+ { name = "wcmatch" },
57
+ { name = "yamllint" },
58
+ ]
59
+ sdist = { url = "https://files.pythonhosted.org/packages/bc/22/4db7a3ab031c7f08f6b1340df9fbc7b730a2ee25ec21047c1a3da445af0a/ansible_lint-26.11.0.tar.gz", hash = "sha256:74813203aa5450d720c3d86b9d0d4ddc51d30e36ac69e1f0d9f7650f5a3b5dd3", size = 876436, upload-time = "2026-11-05T08:50:53.758Z" }
60
+ wheels = [
61
+ { url = "https://files.pythonhosted.org/packages/92/74/15896e47ad1087c1549f7d863631d05d661df0123747622e4cdab88c1428/ansible_lint-26.11.0-py3-none-any.whl", hash = "sha256:b778ea87fa873384b373e8c3fe53a06b3acc7b72ac283e2d2070f76fdf9f8358", size = 349253, upload-time = "2026-11-05T08:50:51.623Z" },
62
+ ]
63
+
64
+ [[package]]
65
+ name = "attrs"
66
+ version = "26.1.0"
67
+ source = { registry = "https://pypi.org/simple" }
68
+ sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" }
69
+ wheels = [
70
+ { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" },
71
+ ]
72
+
73
+ [[package]]
74
+ name = "black"
75
+ version = "26.10.1"
76
+ source = { registry = "https://pypi.org/simple" }
77
+ dependencies = [
78
+ { name = "click" },
79
+ { name = "mypy-extensions" },
80
+ { name = "packaging" },
81
+ { name = "pathspec" },
82
+ { name = "platformdirs" },
83
+ { name = "pytokens" },
84
+ ]
85
+ sdist = { url = "https://files.pythonhosted.org/packages/f8/65/a9611a6ec0a8c88d86e59385da02d68d9533f7e86a05913d20c67be54029/black-26.10.1.tar.gz", hash = "sha256:5f9f83beae62437e060dafd53d7f1fc327e3d3494f74d72ee5c2b73eb90fc4e7", size = 741979, upload-time = "2026-10-10T04:13:40.776Z" }
86
+ wheels = [
87
+ { url = "https://files.pythonhosted.org/packages/99/6b/bc0d39990bd7a71457d669639fa06acf6191bec28bafe8b8103f597cce76/black-26.10.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b6272cfd7e1e8e271f5b0e0207259fe2834687e5cb9b5f620b34a44db9754993", size = 2153812, upload-time = "2026-10-10T04:18:55.041Z" },
88
+ { url = "https://files.pythonhosted.org/packages/85/0e/cc83b88a6b1a51fc051aa4e24663918f0bf4fa9fca98552ee93a125a6780/black-26.10.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:978113a40223a6aaefc17364176a809a320e6b288683841427fff04c6d7b4130", size = 1980352, upload-time = "2026-10-10T04:18:56.849Z" },
89
+ { url = "https://files.pythonhosted.org/packages/7b/0f/4dd24ea0ebadbe05e293c3ea7fbebd4389f50508f714640c9b309a0849d2/black-26.10.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:03c0ddd93bb392e71209903a691767eb366fe1a76deb9509ccbaae9e1f14bb52", size = 2055268, upload-time = "2026-10-10T04:18:58.974Z" },
90
+ { url = "https://files.pythonhosted.org/packages/a2/06/221f8e81891ecf5e4df9c258a11037997f1f218533ffe4fa1dc4db37f19c/black-26.10.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6dba8138cdc99061ef07b958ac082d2aa057b6961d1936f9717c350f02bab5f", size = 1625938, upload-time = "2026-10-10T04:19:00.65Z" },
91
+ { url = "https://files.pythonhosted.org/packages/2b/3b/763d2dd073fc1e2cbf0fe5e584fe96913bf0d14cd725d1116f4b38453adf/black-26.10.1-cp313-cp313-win_arm64.whl", hash = "sha256:d42dd2fac7c342ae67e64ee99c9532e20b2a84e92c79ed3317fa2ef54c801d93", size = 1470752, upload-time = "2026-10-10T04:19:03.009Z" },
92
+ { url = "https://files.pythonhosted.org/packages/b1/28/9dd29175c1db777e6189e2a0bb5f37101adf19c3e509b236c8920f778d0b/black-26.10.1-py3-none-any.whl", hash = "sha256:28842f9a8207cc1df6eb983a35a14c5a0dfcd603d214fe82d84bef552afd2e3a", size = 239267, upload-time = "2026-10-10T04:13:38.808Z" },
93
+ ]
94
+
95
+ [[package]]
96
+ name = "bracex"
97
+ version = "3.2.2"
98
+ source = { registry = "https://pypi.org/simple" }
99
+ sdist = { url = "https://files.pythonhosted.org/packages/2d/1f/b7ce2efe406b87bb3693d587b91bcd8fa9030888870ceff29e4a0d233cb2/bracex-3.2.2.tar.gz", hash = "sha256:d18976160b3ba01a57d5c53a5c8c3aa2506efa9e0a3a62467251f82b8f563fde", size = 34980, upload-time = "2026-11-07T21:28:25.433Z" }
100
+ wheels = [
101
+ { url = "https://files.pythonhosted.org/packages/21/b5/52a750a062db07ba056cde1c52c802b898a96b70953389a0b2247e18f210/bracex-3.2.2-py3-none-any.whl", hash = "sha256:284d59c4656e5a0421ff691402074bd5847049d50410535764a2c522a47095bc", size = 14151, upload-time = "2026-11-07T21:28:24.028Z" },
102
+ ]
103
+
21
104
  [[package]]
22
105
  name = "cffi"
23
106
  version = "2.1.1"
@@ -43,6 +126,15 @@ wheels = [
43
126
  { url = "https://files.pythonhosted.org/packages/01/9a/11f687cb39d6a3504060d5242f04f48c735afb4d3d533958a20594890cb2/cffi-2.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:63bbfd5ded17c4840ac07cd8f1c21ba9d9708141f840b324f422f41b207e3973", size = 180078, upload-time = "2026-08-03T21:20:15.917Z" },
44
127
  ]
45
128
 
129
+ [[package]]
130
+ name = "click"
131
+ version = "8.5.1"
132
+ source = { registry = "https://pypi.org/simple" }
133
+ sdist = { url = "https://files.pythonhosted.org/packages/45/37/edbf1b0707709013c0cf2e8317e03d97ed29bf4cac709fe328904ae92a38/click-8.5.1.tar.gz", hash = "sha256:9bbfada76703fd1d784b83eea012e3e8c6a001a876543ef104fa56aaaeb072b4", size = 399151, upload-time = "2026-11-06T17:37:01.021Z" }
134
+ wheels = [
135
+ { url = "https://files.pythonhosted.org/packages/31/7d/164b7ffc8624fe9efb4221bf9af485d5c1e41b9ae118045d2b6dc5457d8b/click-8.5.1-py3-none-any.whl", hash = "sha256:8bb5376d2006950386f8c6427613334a2acadc5f559cb80e638d00b4df957e37", size = 126163, upload-time = "2026-11-06T17:36:59.287Z" },
136
+ ]
137
+
46
138
  [[package]]
47
139
  name = "cryptography"
48
140
  version = "50.0.2"
@@ -101,9 +193,35 @@ dependencies = [
101
193
  { name = "ansible-core" },
102
194
  ]
103
195
 
196
+ [package.dev-dependencies]
197
+ lint = [
198
+ { name = "ansible-lint" },
199
+ ]
200
+
104
201
  [package.metadata]
105
202
  requires-dist = [{ name = "ansible-core", specifier = "==2.19.9" }]
106
203
 
204
+ [package.metadata.requires-dev]
205
+ lint = [{ name = "ansible-lint", specifier = "==26.11.0" }]
206
+
207
+ [[package]]
208
+ name = "distro"
209
+ version = "1.9.0"
210
+ source = { registry = "https://pypi.org/simple" }
211
+ sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
212
+ wheels = [
213
+ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
214
+ ]
215
+
216
+ [[package]]
217
+ name = "filelock"
218
+ version = "4.2.10"
219
+ source = { registry = "https://pypi.org/simple" }
220
+ sdist = { url = "https://files.pythonhosted.org/packages/a1/cf/05bcdacabdc0aec7dc8e19d76281bd82325df62dae3b6430e9ba57397689/filelock-4.2.10.tar.gz", hash = "sha256:fcc3c0cb8bfe7f4b3bb0e231c04b15dbad068cb3c2515f343d8fde4dce93bd5d", size = 670595, upload-time = "2026-11-09T18:58:00.54Z" }
221
+ wheels = [
222
+ { url = "https://files.pythonhosted.org/packages/ef/90/5d0096a325aa0f83ba7a3968bfb485b67e32c947bdf9cd0fcb4b485be718/filelock-4.2.10-py3-none-any.whl", hash = "sha256:9479aaeb4db20b5a53e3da28493edd24720b66b8a0b84030aa56ccdf243d6857", size = 218040, upload-time = "2026-11-09T18:57:59.088Z" },
223
+ ]
224
+
107
225
  [[package]]
108
226
  name = "jinja2"
109
227
  version = "3.1.6"
@@ -116,6 +234,33 @@ wheels = [
116
234
  { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
117
235
  ]
118
236
 
237
+ [[package]]
238
+ name = "jsonschema"
239
+ version = "4.26.0"
240
+ source = { registry = "https://pypi.org/simple" }
241
+ dependencies = [
242
+ { name = "attrs" },
243
+ { name = "jsonschema-specifications" },
244
+ { name = "referencing" },
245
+ { name = "rpds-py" },
246
+ ]
247
+ sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" }
248
+ wheels = [
249
+ { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" },
250
+ ]
251
+
252
+ [[package]]
253
+ name = "jsonschema-specifications"
254
+ version = "2025.9.1"
255
+ source = { registry = "https://pypi.org/simple" }
256
+ dependencies = [
257
+ { name = "referencing" },
258
+ ]
259
+ sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" }
260
+ wheels = [
261
+ { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" },
262
+ ]
263
+
119
264
  [[package]]
120
265
  name = "markupsafe"
121
266
  version = "3.0.4"
@@ -144,6 +289,15 @@ wheels = [
144
289
  { url = "https://files.pythonhosted.org/packages/9f/a8/9b662783ffaa1149221432a923cee562f78b9cbbb8baa3df9b3753e63e1e/markupsafe-3.0.4-cp313-cp313-win_arm64.whl", hash = "sha256:06de8ef6331f6e822c28d577dc8bf43fe398800477c49498f38fc38b67ff33fc", size = 14189, upload-time = "2026-10-02T23:05:29.917Z" },
145
290
  ]
146
291
 
292
+ [[package]]
293
+ name = "mypy-extensions"
294
+ version = "1.2.0"
295
+ source = { registry = "https://pypi.org/simple" }
296
+ sdist = { url = "https://files.pythonhosted.org/packages/d0/6f/43edf3be2748d0e40974a5ee046a8bb4c9961a4bdd3e7d5c6dcbf32d1af6/mypy_extensions-1.2.0.tar.gz", hash = "sha256:c6e275a07f1c2fc096b62940452d0eaa3f3af7de45d704787866240a7c6d5a51", size = 6566, upload-time = "2026-11-03T16:26:33.692Z" }
297
+ wheels = [
298
+ { url = "https://files.pythonhosted.org/packages/b5/99/7b1adac7e97ce7bbda34792dc6e9b2f5cc5cffd1875883f78a818b2a2687/mypy_extensions-1.2.0-py3-none-any.whl", hash = "sha256:993e7a20aa5671eb41a7132cf555af30062da99729601601b9a8382b11cae768", size = 5114, upload-time = "2026-11-03T16:26:32.731Z" },
299
+ ]
300
+
147
301
  [[package]]
148
302
  name = "packaging"
149
303
  version = "26.3"
@@ -153,6 +307,24 @@ wheels = [
153
307
  { url = "https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl", hash = "sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c", size = 129956, upload-time = "2026-08-04T18:15:27.159Z" },
154
308
  ]
155
309
 
310
+ [[package]]
311
+ name = "pathspec"
312
+ version = "1.1.1"
313
+ source = { registry = "https://pypi.org/simple" }
314
+ sdist = { url = "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz", hash = "sha256:17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a", size = 135180, upload-time = "2026-04-27T01:46:08.907Z" }
315
+ wheels = [
316
+ { url = "https://files.pythonhosted.org/packages/f1/d9/7fb5aa316bc299258e68c73ba3bddbc499654a07f151cba08f6153988714/pathspec-1.1.1-py3-none-any.whl", hash = "sha256:a00ce642f577bf7f473932318056212bc4f8bfdf53128c78bbd5af0b9b20b189", size = 57328, upload-time = "2026-04-27T01:46:07.06Z" },
317
+ ]
318
+
319
+ [[package]]
320
+ name = "platformdirs"
321
+ version = "4.14.5"
322
+ source = { registry = "https://pypi.org/simple" }
323
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/9a/8475be631fb33ca1466915e70fd594c42a4e84b512db650eaa14f90fe75d/platformdirs-4.14.5.tar.gz", hash = "sha256:32e9921db7385aa8169bee4fe9501923e5715ba9fb7ea3b9c69b0d8e0639f7a1", size = 71464, upload-time = "2026-11-04T16:54:50.358Z" }
324
+ wheels = [
325
+ { url = "https://files.pythonhosted.org/packages/7c/ae/ecbbbfd0e4343a6a521d49b3c8656fb4643dee2a4f6eaf941cf3280391a8/platformdirs-4.14.5-py3-none-any.whl", hash = "sha256:8e778c6fa421516f4d3369449f8df3b0e1b72c0e0005fe88e3ec772d0a14e750", size = 35402, upload-time = "2026-11-04T16:54:48.875Z" },
326
+ ]
327
+
156
328
  [[package]]
157
329
  name = "pycparser"
158
330
  version = "3.12"
@@ -162,6 +334,20 @@ wheels = [
162
334
  { url = "https://files.pythonhosted.org/packages/b6/ae/315bbeee126a769cbde43cf4908acb4b40408d8bf8bbf334dd7368d8dadf/pycparser-3.12-py3-none-any.whl", hash = "sha256:a076f3f489b39b43ed13563a1dafe42447445bec2e294154ec5c57fcdda4c85e", size = 62600, upload-time = "2026-10-22T13:40:02.021Z" },
163
335
  ]
164
336
 
337
+ [[package]]
338
+ name = "pytokens"
339
+ version = "0.4.1"
340
+ source = { registry = "https://pypi.org/simple" }
341
+ sdist = { url = "https://files.pythonhosted.org/packages/b6/34/b4e015b99031667a7b960f888889c5bd34ef585c85e1cb56a594b92836ac/pytokens-0.4.1.tar.gz", hash = "sha256:292052fe80923aae2260c073f822ceba21f3872ced9a68bb7953b348e561179a", size = 23015, upload-time = "2026-01-30T01:03:45.924Z" }
342
+ wheels = [
343
+ { url = "https://files.pythonhosted.org/packages/cb/dc/08b1a080372afda3cceb4f3c0a7ba2bde9d6a5241f1edb02a22a019ee147/pytokens-0.4.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8bdb9d0ce90cbf99c525e75a2fa415144fd570a1ba987380190e8b786bc6ef9b", size = 160720, upload-time = "2026-01-30T01:03:13.843Z" },
344
+ { url = "https://files.pythonhosted.org/packages/64/0c/41ea22205da480837a700e395507e6a24425151dfb7ead73343d6e2d7ffe/pytokens-0.4.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5502408cab1cb18e128570f8d598981c68a50d0cbd7c61312a90507cd3a1276f", size = 254204, upload-time = "2026-01-30T01:03:14.886Z" },
345
+ { url = "https://files.pythonhosted.org/packages/e0/d2/afe5c7f8607018beb99971489dbb846508f1b8f351fcefc225fcf4b2adc0/pytokens-0.4.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:29d1d8fb1030af4d231789959f21821ab6325e463f0503a61d204343c9b355d1", size = 268423, upload-time = "2026-01-30T01:03:15.936Z" },
346
+ { url = "https://files.pythonhosted.org/packages/68/d4/00ffdbd370410c04e9591da9220a68dc1693ef7499173eb3e30d06e05ed1/pytokens-0.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:970b08dd6b86058b6dc07efe9e98414f5102974716232d10f32ff39701e841c4", size = 266859, upload-time = "2026-01-30T01:03:17.458Z" },
347
+ { url = "https://files.pythonhosted.org/packages/a7/c9/c3161313b4ca0c601eeefabd3d3b576edaa9afdefd32da97210700e47652/pytokens-0.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:9bd7d7f544d362576be74f9d5901a22f317efc20046efe2034dced238cbbfe78", size = 103520, upload-time = "2026-01-30T01:03:18.652Z" },
348
+ { url = "https://files.pythonhosted.org/packages/c6/78/397db326746f0a342855b81216ae1f0a32965deccfd7c830a2dbc66d2483/pytokens-0.4.1-py3-none-any.whl", hash = "sha256:26cef14744a8385f35d0e095dc8b3a7583f6c953c2e3d269c7f82484bf5ad2de", size = 13729, upload-time = "2026-01-30T01:03:45.029Z" },
349
+ ]
350
+
165
351
  [[package]]
166
352
  name = "pyyaml"
167
353
  version = "6.0.3"
@@ -180,6 +366,19 @@ wheels = [
180
366
  { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" },
181
367
  ]
182
368
 
369
+ [[package]]
370
+ name = "referencing"
371
+ version = "0.37.0"
372
+ source = { registry = "https://pypi.org/simple" }
373
+ dependencies = [
374
+ { name = "attrs" },
375
+ { name = "rpds-py" },
376
+ ]
377
+ sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" }
378
+ wheels = [
379
+ { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" },
380
+ ]
381
+
183
382
  [[package]]
184
383
  name = "resolvelib"
185
384
  version = "1.2.1"
@@ -188,3 +387,69 @@ sdist = { url = "https://files.pythonhosted.org/packages/1d/14/4669927e06631070e
188
387
  wheels = [
189
388
  { url = "https://files.pythonhosted.org/packages/e2/23/c941a0d0353681ca138489983c4309e0f5095dfd902e1357004f2357ddf2/resolvelib-1.2.1-py3-none-any.whl", hash = "sha256:fb06b66c8da04172d9e72a21d7d06186d8919e32ae5ab5cdf5b9d920be805ac2", size = 18737, upload-time = "2025-10-11T01:07:43.081Z" },
190
389
  ]
390
+
391
+ [[package]]
392
+ name = "rpds-py"
393
+ version = "2026.9.1"
394
+ source = { registry = "https://pypi.org/simple" }
395
+ sdist = { url = "https://files.pythonhosted.org/packages/42/68/3bd46b8a5e01d3c2ebdf9c5e9497912e3fe0cde02bac21a7130ca866e403/rpds_py-2026.9.1.tar.gz", hash = "sha256:4793ef7f78268b124b73fa933440f01d258bbae01de9fa53e9080c9ab0425a12", size = 63948, upload-time = "2026-10-04T16:32:36.469Z" }
396
+ wheels = [
397
+ { url = "https://files.pythonhosted.org/packages/83/ea/ee88fd9e756ff93fb6b1182a47ec09504a242620e33ce1d20679efefe841/rpds_py-2026.9.1-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:a36b70596407634ca82d4b989a3729074a008537a0522e4c8046a67c729103e9", size = 346229, upload-time = "2026-10-04T16:29:38.82Z" },
398
+ { url = "https://files.pythonhosted.org/packages/57/71/a097d6552f837500fc36e6b23d09cfb9890c3cc47531f9ca64e149799615/rpds_py-2026.9.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:eba5d173f7d5708b22a93815017a4611873ed54db9f268077c0dd1ed99cfc858", size = 340514, upload-time = "2026-10-04T16:29:40.405Z" },
399
+ { url = "https://files.pythonhosted.org/packages/bd/b7/497e85768bf4e0d8ddbaa096a4cac31d1509251dee2728a8490aa367e0b5/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:457866b85daf5034296666168b84a69e0b2e89dc4f1af102b46f6448a60b9063", size = 372611, upload-time = "2026-10-04T16:29:41.778Z" },
400
+ { url = "https://files.pythonhosted.org/packages/52/4b/74ab4108916250b6e198e0d3af05bc6835eb046315f22f7a0ceb49667c5a/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a3a52a3ba86436ab3aef510fbe21512abc2ddd1993005dfe50514bd2284ef025", size = 377712, upload-time = "2026-10-04T16:29:43.242Z" },
401
+ { url = "https://files.pythonhosted.org/packages/0c/8e/067e77d9d7b3cc793c9d909b7e97e7aadbbb1fb094093b6876902cc96d38/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d7841166b7fa64c9c56404617ae4341448847482d45933b13135d26c130519e5", size = 488106, upload-time = "2026-10-04T16:29:44.692Z" },
402
+ { url = "https://files.pythonhosted.org/packages/3c/b4/c5aae6c2dde269bf955f6b7d35065c655a57e47750d9668052ad67e74dda/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:926bdd3e3b5998ddf70cc64bc8cf57209571f9044542913afb673799fec77dd0", size = 390795, upload-time = "2026-10-04T16:29:46.129Z" },
403
+ { url = "https://files.pythonhosted.org/packages/a0/36/76fab39973ee11e7f9f357c55138197bb01c86f6502cb76487e3b4f42db0/rpds_py-2026.9.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7868b85224291c6cb6759f9b5adb9745f486d226f62b16a614dd5a2a5ab2b35b", size = 370794, upload-time = "2026-10-04T16:29:47.603Z" },
404
+ { url = "https://files.pythonhosted.org/packages/3d/fe/cd2a80e6d7b871937a60e935c5d507aa390d143f4ff3636f640b9733d5df/rpds_py-2026.9.1-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:3cd182d7291d29b92c521a0069d9c01ba6193628a9a105531d11b40a6d731a33", size = 375673, upload-time = "2026-10-04T16:29:49.223Z" },
405
+ { url = "https://files.pythonhosted.org/packages/6c/18/7464a9953724e55a3b3206062fa0ffdeaa519584c6aabf65d3956d94f131/rpds_py-2026.9.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e6ea1cda8d8c688278430e4268a42f5e5da3bdd74578dfadc0820c3f1766ce83", size = 398758, upload-time = "2026-10-04T16:29:50.601Z" },
406
+ { url = "https://files.pythonhosted.org/packages/c0/86/1534b436700fd49ff411063b7c4d7e938adfabf90895b6cf1622d5a7d1f6/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5943980471829f6de242a20b109de3111ba6b77e3af0ffc587028ac854b05e6c", size = 550370, upload-time = "2026-10-04T16:29:52.002Z" },
407
+ { url = "https://files.pythonhosted.org/packages/57/1c/e1fa82a8a01e3c5820f3ba98a8b2673f642128eb368fa88871b01dd2c909/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:76d3af9732d2dab69f28179b40ba2d87e2f1d5824b4a694780aa787d685e8f36", size = 613106, upload-time = "2026-10-04T16:29:53.655Z" },
408
+ { url = "https://files.pythonhosted.org/packages/29/55/b20b8c4c3dde8755bfcd5b08492a02d0cd2e26929aedf6199ca2a377d42a/rpds_py-2026.9.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:78326f4cb4427a56ba4996c0762b63be45f06b85f086526420d2b3a66e40f84d", size = 578235, upload-time = "2026-10-04T16:29:55.157Z" },
409
+ { url = "https://files.pythonhosted.org/packages/55/42/df3f7bbc3f7ab37a8a9db8d6c2ff2c985422899f1f7926afbf7ec3c0b8b4/rpds_py-2026.9.1-cp313-cp313-win32.whl", hash = "sha256:172e47169583f46ce118cbec68e6795d0da0f4606b488b6434f8276bca0a058c", size = 205293, upload-time = "2026-10-04T16:29:56.669Z" },
410
+ { url = "https://files.pythonhosted.org/packages/31/9c/ba5a9569d719bfdd6ce863df4133ac6a1658cf1b07cc3534c31db729fbbc/rpds_py-2026.9.1-cp313-cp313-win_amd64.whl", hash = "sha256:3e93b2cd69a9830be33e03945cd7cda940a0a8bfcfbff41d6144f0cb0d3d8bd9", size = 222409, upload-time = "2026-10-04T16:29:58.049Z" },
411
+ { url = "https://files.pythonhosted.org/packages/35/72/f28ca566f6c23c35bbf7445f65eb0364577b25a026305995e24f78b83d94/rpds_py-2026.9.1-cp313-cp313-win_arm64.whl", hash = "sha256:d151e148117294133bf8af7eeace085e7e87432db15ab6adf640330298a47f6f", size = 217681, upload-time = "2026-10-04T16:29:59.449Z" },
412
+ ]
413
+
414
+ [[package]]
415
+ name = "ruamel-yaml"
416
+ version = "0.19.1"
417
+ source = { registry = "https://pypi.org/simple" }
418
+ sdist = { url = "https://files.pythonhosted.org/packages/c7/3b/ebda527b56beb90cb7652cb1c7e4f91f48649fbcd8d2eb2fb6e77cd3329b/ruamel_yaml-0.19.1.tar.gz", hash = "sha256:53eb66cd27849eff968ebf8f0bf61f46cdac2da1d1f3576dd4ccee9b25c31993", size = 142709, upload-time = "2026-01-02T16:50:31.84Z" }
419
+ wheels = [
420
+ { url = "https://files.pythonhosted.org/packages/b8/0c/51f6841f1d84f404f92463fc2b1ba0da357ca1e3db6b7fbda26956c3b82a/ruamel_yaml-0.19.1-py3-none-any.whl", hash = "sha256:27592957fedf6e0b62f281e96effd28043345e0e66001f97683aa9a40c667c93", size = 118102, upload-time = "2026-01-02T16:50:29.201Z" },
421
+ ]
422
+
423
+ [[package]]
424
+ name = "subprocess-tee"
425
+ version = "0.4.2"
426
+ source = { registry = "https://pypi.org/simple" }
427
+ sdist = { url = "https://files.pythonhosted.org/packages/d7/22/991efbf35bc811dfe7edcd749253f0931d2d4838cf55176132633e1c82a7/subprocess_tee-0.4.2.tar.gz", hash = "sha256:91b2b4da3aae9a7088d84acaf2ea0abee3f4fd9c0d2eae69a9b9122a71476590", size = 14951, upload-time = "2024-06-17T19:51:51.249Z" }
428
+ wheels = [
429
+ { url = "https://files.pythonhosted.org/packages/4d/ab/e3a3be062cd544b2803760ff707dee38f0b1cb5685b2446de0ec19be28d9/subprocess_tee-0.4.2-py3-none-any.whl", hash = "sha256:21942e976715af4a19a526918adb03a8a27a8edab959f2d075b777e3d78f532d", size = 5249, upload-time = "2024-06-17T19:51:15.949Z" },
430
+ ]
431
+
432
+ [[package]]
433
+ name = "wcmatch"
434
+ version = "12.0.1"
435
+ source = { registry = "https://pypi.org/simple" }
436
+ dependencies = [
437
+ { name = "bracex" },
438
+ ]
439
+ sdist = { url = "https://files.pythonhosted.org/packages/2e/17/bf44790f97414e37885830139933b4e96faac71560768ae9f9e458dbd936/wcmatch-12.0.1.tar.gz", hash = "sha256:4ea6acef75b657bf71ac8fa9520165bf5ae55120c4e655895a5b7d4c69896c5e", size = 163680, upload-time = "2026-11-07T21:45:37.072Z" }
440
+ wheels = [
441
+ { url = "https://files.pythonhosted.org/packages/8a/24/8a5bbb3e73962b5bc901966f926b4fe25cbc9d1170ad9b4d9938441dbdc3/wcmatch-12.0.1-py3-none-any.whl", hash = "sha256:0e11b0bf1700bc8222ad5e7dc79fb326d9e0304458c7e2c25419ab6d26d07d8e", size = 51084, upload-time = "2026-11-07T21:45:35.661Z" },
442
+ ]
443
+
444
+ [[package]]
445
+ name = "yamllint"
446
+ version = "1.38.0"
447
+ source = { registry = "https://pypi.org/simple" }
448
+ dependencies = [
449
+ { name = "pathspec" },
450
+ { name = "pyyaml" },
451
+ ]
452
+ sdist = { url = "https://files.pythonhosted.org/packages/28/a0/8fc2d68e132cf918f18273fdc8a1b8432b60d75ac12fdae4b0ef5c9d2e8d/yamllint-1.38.0.tar.gz", hash = "sha256:09e5f29531daab93366bb061e76019d5e91691ef0a40328f04c927387d1d364d", size = 142446, upload-time = "2026-01-13T07:47:53.276Z" }
453
+ wheels = [
454
+ { url = "https://files.pythonhosted.org/packages/05/92/aed08e68de6e6a3d7c2328ce7388072cd6affc26e2917197430b646aed02/yamllint-1.38.0-py3-none-any.whl", hash = "sha256:fc394a5b3be980a4062607b8fdddc0843f4fa394152b6da21722f5d59013c220", size = 68940, upload-time = "2026-01-13T07:47:51.343Z" },
455
+ ]