team 0.1.1 → 0.2.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 (127) hide show
  1. package/README.md +38 -12
  2. package/dist/ansi.d.ts +13 -0
  3. package/dist/ansi.js +75 -0
  4. package/dist/approve/approval.d.ts +11 -0
  5. package/dist/approve/approval.js +80 -8
  6. package/dist/approve/fingerprint.d.ts +29 -6
  7. package/dist/approve/fingerprint.js +57 -32
  8. package/dist/budgets/gate.d.ts +5 -4
  9. package/dist/budgets/gate.js +23 -14
  10. package/dist/budgets/readings.d.ts +57 -9
  11. package/dist/budgets/readings.js +134 -18
  12. package/dist/budgets/run.js +8 -2
  13. package/dist/budgets/table.d.ts +4 -2
  14. package/dist/budgets/table.js +29 -11
  15. package/dist/cli.d.ts +2 -1
  16. package/dist/cli.js +8 -5
  17. package/dist/commands/add.d.ts +0 -1
  18. package/dist/commands/add.js +18 -9
  19. package/dist/commands/approve.js +17 -1
  20. package/dist/commands/doctor.d.ts +17 -1
  21. package/dist/commands/doctor.js +128 -8
  22. package/dist/commands/down.d.ts +6 -2
  23. package/dist/commands/down.js +40 -14
  24. package/dist/commands/init.d.ts +1 -1
  25. package/dist/commands/init.js +4 -2
  26. package/dist/commands/remove.d.ts +2 -0
  27. package/dist/commands/remove.js +26 -4
  28. package/dist/commands/status.d.ts +2 -0
  29. package/dist/commands/status.js +22 -3
  30. package/dist/commands/up.d.ts +3 -1
  31. package/dist/commands/up.js +12 -6
  32. package/dist/commands/watch.d.ts +4 -0
  33. package/dist/commands/watch.js +99 -19
  34. package/dist/conformance/adapter.d.ts +5 -0
  35. package/dist/conformance/adapter.js +44 -0
  36. package/dist/file/check.d.ts +20 -0
  37. package/dist/file/check.js +112 -0
  38. package/dist/file/lines.js +13 -2
  39. package/dist/file/sections/budgets.d.ts +15 -0
  40. package/dist/file/sections/budgets.js +180 -0
  41. package/dist/file/sections/coordinator.d.ts +2 -0
  42. package/dist/file/sections/coordinator.js +11 -0
  43. package/dist/file/sections/format.d.ts +2 -0
  44. package/dist/file/sections/format.js +13 -0
  45. package/dist/file/sections/identity.d.ts +2 -0
  46. package/dist/file/sections/identity.js +103 -0
  47. package/dist/file/sections/index.d.ts +9 -0
  48. package/dist/file/sections/index.js +43 -0
  49. package/dist/file/sections/lead.d.ts +4 -0
  50. package/dist/file/sections/lead.js +18 -0
  51. package/dist/file/sections/limits.d.ts +2 -0
  52. package/dist/file/sections/limits.js +43 -0
  53. package/dist/file/sections/machine.d.ts +2 -0
  54. package/dist/file/sections/machine.js +38 -0
  55. package/dist/file/sections/operator.d.ts +2 -0
  56. package/dist/file/sections/operator.js +11 -0
  57. package/dist/file/sections/project.d.ts +2 -0
  58. package/dist/file/sections/project.js +13 -0
  59. package/dist/file/sections/rules.d.ts +2 -0
  60. package/dist/file/sections/rules.js +9 -0
  61. package/dist/file/sections/seats.d.ts +7 -0
  62. package/dist/file/sections/seats.js +161 -0
  63. package/dist/file/sections/section.d.ts +32 -0
  64. package/dist/file/sections/section.js +7 -0
  65. package/dist/file/sections/session.d.ts +2 -0
  66. package/dist/file/sections/session.js +16 -0
  67. package/dist/file/sections/tools.d.ts +2 -0
  68. package/dist/file/sections/tools.js +43 -0
  69. package/dist/file/sections/trust.d.ts +2 -0
  70. package/dist/file/sections/trust.js +20 -0
  71. package/dist/file/sections/units.d.ts +16 -0
  72. package/dist/file/sections/units.js +8 -0
  73. package/dist/file/sections/visibility.d.ts +2 -0
  74. package/dist/file/sections/visibility.js +12 -0
  75. package/dist/file/sections/watch-checks.d.ts +9 -0
  76. package/dist/file/sections/watch-checks.js +14 -0
  77. package/dist/file/sections/watch.d.ts +14 -0
  78. package/dist/file/sections/watch.js +111 -0
  79. package/dist/file/sections/workspace.d.ts +4 -0
  80. package/dist/file/sections/workspace.js +77 -0
  81. package/dist/file/types.d.ts +6 -0
  82. package/dist/file/validate.d.ts +4 -12
  83. package/dist/file/validate.js +45 -668
  84. package/dist/herdr.d.ts +5 -0
  85. package/dist/herdr.js +51 -12
  86. package/dist/launch/agent.d.ts +1 -0
  87. package/dist/launch/agent.js +13 -0
  88. package/dist/launch/deliver.d.ts +7 -1
  89. package/dist/launch/deliver.js +159 -7
  90. package/dist/launch/execute.d.ts +4 -2
  91. package/dist/launch/execute.js +36 -13
  92. package/dist/launch/plan.js +16 -2
  93. package/dist/launch/rules.d.ts +4 -2
  94. package/dist/launch/rules.js +17 -3
  95. package/dist/profiles/antigravity.yaml +25 -2
  96. package/dist/profiles/claude-code.yaml +51 -6
  97. package/dist/profiles/codex.yaml +14 -1
  98. package/dist/profiles/cursor.yaml +46 -3
  99. package/dist/profiles/overrides.d.ts +67 -0
  100. package/dist/profiles/overrides.js +190 -0
  101. package/dist/profiles/profile.d.ts +3 -0
  102. package/dist/profiles/profile.js +5 -1
  103. package/dist/state.d.ts +9 -4
  104. package/dist/state.js +37 -0
  105. package/dist/status/compare.js +7 -4
  106. package/dist/status/statusline.js +3 -1
  107. package/dist/store/store.d.ts +5 -0
  108. package/dist/version.d.ts +1 -0
  109. package/dist/version.js +7 -0
  110. package/dist/watch/check.d.ts +1 -0
  111. package/dist/watch/checks/budget.js +50 -51
  112. package/dist/watch/dialect.d.ts +6 -1
  113. package/dist/watch/dialect.js +68 -12
  114. package/dist/watch/pass.d.ts +10 -2
  115. package/dist/watch/pass.js +70 -29
  116. package/dist/watch/screen-core.d.ts +41 -4
  117. package/dist/watch/screen-core.js +510 -89
  118. package/dist/watch/screen-data.d.ts +34 -0
  119. package/dist/watch/screen-file.d.ts +8 -2
  120. package/dist/watch/screen-file.js +344 -39
  121. package/dist/watch/screen-profile.d.ts +19 -0
  122. package/dist/watch/screen-profile.js +1 -0
  123. package/dist/watch/screen.d.ts +25 -6
  124. package/dist/watch/screen.js +48 -9
  125. package/examples/team.yaml +8 -0
  126. package/package.json +9 -3
  127. package/schema/team.schema.json +498 -0
@@ -0,0 +1,180 @@
1
+ import { measureSchema, DURATION, PERCENT } from "./units.js";
2
+ import { valueOf } from "./section.js";
3
+ import { defaultWatch } from "./watch.js";
4
+ export const budgets = {
5
+ name: 'budgets',
6
+ owner: true,
7
+ after: ['watch'],
8
+ validate(entry, ctx) {
9
+ const watched = valueOf(ctx, 'watch');
10
+ return readBudgets(entry, ctx.check, watched.watch.quotaMarks, watched.legacyMarks);
11
+ },
12
+ schema: {
13
+ type: 'object',
14
+ additionalProperties: false,
15
+ properties: {
16
+ stale_after: measureSchema(DURATION),
17
+ check_every: measureSchema(DURATION),
18
+ marks: { type: 'array', minItems: 1, items: { type: 'number', exclusiveMinimum: 0, maximum: 100 } },
19
+ accounts: {
20
+ type: 'object',
21
+ additionalProperties: {
22
+ type: 'object',
23
+ additionalProperties: false,
24
+ properties: {
25
+ kind: { enum: ['subscription', 'spend'] },
26
+ shared: { type: 'boolean' },
27
+ reserve: measureSchema(PERCENT),
28
+ floor: { type: 'string', pattern: '^[0-9]+(?:\\.[0-9]{1,4})? [A-Z]{3}$' },
29
+ sources: { type: 'array', minItems: 1, items: { enum: ['check', 'status_line'] } },
30
+ check: { type: 'string', pattern: '^\\S+$' },
31
+ },
32
+ required: ['kind'],
33
+ allOf: [
34
+ {
35
+ if: { properties: { kind: { const: 'subscription' } }, required: ['kind'] },
36
+ then: { required: ['reserve'] },
37
+ else: { required: ['floor'] },
38
+ },
39
+ ],
40
+ },
41
+ $comment: 'a subscription has a reserve, a spend account a floor; a spend account never reads a screen',
42
+ },
43
+ },
44
+ },
45
+ };
46
+ /**
47
+ * The budget values a file that sets none runs with — and, since the section is the owner's, the
48
+ * values a file runs with until the owner approves what it sets instead. No accounts: nothing is
49
+ * budgeted until the owner names one.
50
+ */
51
+ export function defaultBudgets() {
52
+ return { staleAfter: 30 * 60, checkEvery: 10 * 60, marks: defaultWatch().quotaMarks, accounts: {} };
53
+ }
54
+ function readBudgets(entry, check, fallbackMarks, legacyMarks) {
55
+ const base = { ...defaultBudgets(), marks: fallbackMarks };
56
+ if (!entry)
57
+ return { budgets: base, declared: new Set() };
58
+ const fields = check.fields(entry.value, 'budgets', ['stale_after', 'check_every', 'marks', 'accounts']);
59
+ // Every name the accounts map writes, whether or not the account itself reads cleanly: a seat
60
+ // naming one of these is not also told it named nothing.
61
+ const accounts = fields.get('accounts');
62
+ const declared = new Set(accounts?.value.kind === 'map' ? accounts.value.entries.map((account) => account.key) : []);
63
+ const marks = percentList(fields.get('marks'), 'budgets.marks', check);
64
+ if (marks && legacyMarks) {
65
+ check.warnings.push({ line: fields.get('marks')?.line ?? entry.line, message: 'budgets.marks replaces watch.quota_marks' });
66
+ }
67
+ return {
68
+ budgets: {
69
+ staleAfter: check.measure(fields.get('stale_after'), 'budgets.stale_after', DURATION, '30m') ?? base.staleAfter,
70
+ checkEvery: check.measure(fields.get('check_every'), 'budgets.check_every', DURATION, '10m') ?? base.checkEvery,
71
+ marks: marks ?? fallbackMarks,
72
+ accounts: readAccounts(fields.get('accounts'), check),
73
+ },
74
+ declared,
75
+ };
76
+ }
77
+ function percentList(entry, name, check) {
78
+ if (!entry)
79
+ return null;
80
+ const node = entry.value;
81
+ const numbers = node.kind === 'seq' ? node.items.map((item) => (item.kind === 'scalar' ? item.value : null)) : null;
82
+ if (numbers && numbers.length > 0 && numbers.every((n) => typeof n === 'number' && n > 0 && n <= 100))
83
+ return numbers;
84
+ check.fail(node.line, `${name} must be a list of percentages, such as [50, 75, 90]`);
85
+ return null;
86
+ }
87
+ function readAccounts(entry, check) {
88
+ const accounts = {};
89
+ if (!entry)
90
+ return accounts;
91
+ if (entry.value.kind !== 'map') {
92
+ check.fail(entry.value.line, 'budgets.accounts must be a map');
93
+ return accounts;
94
+ }
95
+ for (const account of entry.value.entries) {
96
+ const fields = check.fields(account.value, `budgets.accounts.${account.key}`, ['kind', 'shared', 'reserve', 'floor', 'sources', 'check']);
97
+ const kind = check.oneOf(fields.get('kind'), `budgets.accounts.${account.key}.kind`, ['subscription', 'spend']);
98
+ if (!kind) {
99
+ check.fail(account.line, `budgets.accounts.${account.key} needs kind`);
100
+ continue;
101
+ }
102
+ const command = check.text(fields.get('check'), `budgets.accounts.${account.key}.check`) ?? null;
103
+ if (command && /\s/.test(command)) {
104
+ check.fail(fields.get('check')?.line ?? account.line, `budgets.accounts.${account.key}.check names one command, with no arguments; use a wrapper`);
105
+ }
106
+ const sources = sourcesOf(fields.get('sources'), account.key, kind, Boolean(command) && !/\s/.test(command ?? ''), check, account.line);
107
+ if (sources.includes('check') && !command)
108
+ check.fail(account.line, `budgets.accounts.${account.key} needs check`);
109
+ if (command && !/\s/.test(command) && !sources.includes('check')) {
110
+ check.fail(fields.get('check')?.line ?? account.line, `budgets.accounts.${account.key}.check needs sources to list check`);
111
+ }
112
+ const measured = kind === 'subscription'
113
+ ? check.measure(fields.get('reserve'), `budgets.accounts.${account.key}.reserve`, PERCENT, '20%')
114
+ : undefined;
115
+ const reserve = measured !== undefined && measured > 0 && measured <= 100 ? measured : null;
116
+ if (kind === 'subscription' && measured !== undefined && (measured <= 0 || measured > 100)) {
117
+ check.fail(fields.get('reserve')?.line ?? account.line, `budgets.accounts.${account.key}.reserve must be a percentage above 0 and at most 100, such as 20%`);
118
+ }
119
+ if (kind === 'subscription' && !fields.get('reserve'))
120
+ check.fail(account.line, `budgets.accounts.${account.key} needs reserve`);
121
+ if (kind === 'spend' && fields.get('reserve'))
122
+ check.fail(fields.get('reserve')?.line ?? account.line, 'a spend account has a floor, not a reserve');
123
+ const floor = kind === 'spend' ? money(fields.get('floor'), account.key, check) : null;
124
+ if (kind === 'spend' && !fields.get('floor'))
125
+ check.fail(account.line, `budgets.accounts.${account.key} needs floor`);
126
+ if (kind === 'subscription' && fields.get('floor'))
127
+ check.fail(fields.get('floor')?.line ?? account.line, 'a subscription has a reserve, not a floor');
128
+ accounts[account.key] = {
129
+ kind,
130
+ shared: check.flag(fields.get('shared'), `budgets.accounts.${account.key}.shared`),
131
+ reserve,
132
+ floor,
133
+ sources,
134
+ check: command,
135
+ };
136
+ }
137
+ return accounts;
138
+ }
139
+ function sourcesOf(entry, account, kind, hasCheck, check, line) {
140
+ if (!entry) {
141
+ if (hasCheck)
142
+ return ['check'];
143
+ if (kind === 'subscription')
144
+ return ['status_line'];
145
+ check.fail(line, `budgets.accounts.${account} needs check`);
146
+ return [];
147
+ }
148
+ const items = check.list(entry, `budgets.accounts.${account}.sources`);
149
+ if (items.length === 0) {
150
+ check.fail(entry.line, `budgets.accounts.${account} needs a source`);
151
+ return [];
152
+ }
153
+ const sources = [];
154
+ for (const item of items) {
155
+ if (item.value !== 'check' && item.value !== 'status_line') {
156
+ check.fail(item.line, `budgets.accounts.${account}.sources must be check or status_line`);
157
+ continue;
158
+ }
159
+ if (kind === 'spend' && item.value === 'status_line') {
160
+ check.fail(item.line, 'a spend account never reads a screen');
161
+ continue;
162
+ }
163
+ if (sources.includes(item.value))
164
+ check.fail(item.line, `budgets.accounts.${account}.sources lists ${item.value} twice`);
165
+ else
166
+ sources.push(item.value);
167
+ }
168
+ return sources;
169
+ }
170
+ function money(entry, account, check) {
171
+ if (!entry)
172
+ return null;
173
+ const raw = entry.value.kind === 'scalar' ? entry.value.raw : '';
174
+ const match = /^([0-9]+(?:\.[0-9]{1,4})?) ([A-Z]{3})$/.exec(raw);
175
+ if (!match || match[1] === undefined || match[2] === undefined) {
176
+ check.fail(entry.line, `budgets.accounts.${account}.floor is an amount and a currency, such as 5 USD`);
177
+ return null;
178
+ }
179
+ return { amount: Number(match[1]), currency: match[2] };
180
+ }
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const coordinator: Section;
@@ -0,0 +1,11 @@
1
+ import { valueOf } from "./section.js";
2
+ import { readLead } from "./lead.js";
3
+ export const coordinator = {
4
+ name: 'coordinator',
5
+ owner: true,
6
+ after: ['seats'],
7
+ validate(entry, ctx) {
8
+ return readLead(entry, 'coordinator', valueOf(ctx, 'seats'), ctx.broken, ctx.check);
9
+ },
10
+ schema: { type: 'string', minLength: 1, $comment: 'must name a declared seat: one, without count, neither parked nor stopped' },
11
+ };
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const format: Section;
@@ -0,0 +1,13 @@
1
+ export const format = {
2
+ name: 'format',
3
+ owner: false,
4
+ after: [],
5
+ validate(entry, ctx) {
6
+ if (!entry)
7
+ ctx.check.fail(1, 'format is required: "format: 1"');
8
+ else if (!(entry.value.kind === 'scalar' && entry.value.value === 1 && !entry.value.quoted)) {
9
+ ctx.check.fail(entry.line, 'format must be 1: this version of team reads no other');
10
+ }
11
+ },
12
+ schema: { const: 1, $comment: 'unquoted: this version of team reads no other' },
13
+ };
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const identity: Section;
@@ -0,0 +1,103 @@
1
+ import { DEFAULT_COMMIT_TEMPLATE, DEFAULT_FORBIDDEN, DEFAULT_PR_TEMPLATE, isTrailerTemplate, templateProblem, } from "../signature.js";
2
+ const POSITIONS = ['last-line', 'trailer', 'anywhere'];
3
+ export const identity = {
4
+ name: 'identity',
5
+ owner: true,
6
+ after: [],
7
+ validate(entry, ctx) {
8
+ return readIdentity(entry, ctx.check);
9
+ },
10
+ schema: {
11
+ type: 'object',
12
+ additionalProperties: false,
13
+ properties: {
14
+ signature: {
15
+ type: 'object',
16
+ additionalProperties: false,
17
+ properties: {
18
+ template: { type: 'string' },
19
+ commits: {
20
+ type: 'object',
21
+ additionalProperties: false,
22
+ properties: {
23
+ template: { type: 'string' },
24
+ position: { enum: POSITIONS },
25
+ exempt: { type: 'array', items: { const: 'merge' } },
26
+ },
27
+ },
28
+ pull_requests: {
29
+ type: 'object',
30
+ additionalProperties: false,
31
+ properties: {
32
+ template: { type: 'string' },
33
+ position: { enum: POSITIONS },
34
+ },
35
+ },
36
+ },
37
+ },
38
+ humans: { type: 'array', items: { type: 'string', minLength: 1 } },
39
+ since: { type: 'string' },
40
+ forbidden: { type: 'array', items: { type: 'string' } },
41
+ forbidden_public: { type: 'array', items: { type: 'string' } },
42
+ },
43
+ $comment: 'forbidden and forbidden_public are regular expressions; a template in the position "trailer" must read as a git trailer',
44
+ },
45
+ };
46
+ function readIdentity(entry, check) {
47
+ const identity = check.fields(entry?.value, 'identity', ['signature', 'humans', 'since', 'forbidden', 'forbidden_public']);
48
+ const signature = check.fields(identity.get('signature')?.value, 'identity.signature', ['template', 'commits', 'pull_requests']);
49
+ const commits = check.fields(signature.get('commits')?.value, 'identity.signature.commits', ['template', 'position', 'exempt']);
50
+ const prs = check.fields(signature.get('pull_requests')?.value, 'identity.signature.pull_requests', ['template', 'position', 'exempt']);
51
+ const base = readTemplate(signature.get('template'), 'identity.signature.template', check) ?? DEFAULT_COMMIT_TEMPLATE;
52
+ const commitTemplate = readTemplate(commits.get('template'), 'identity.signature.commits.template', check) ?? base;
53
+ const prTemplate = readTemplate(prs.get('template'), 'identity.signature.pull_requests.template', check) ?? DEFAULT_PR_TEMPLATE;
54
+ const commitPosition = check.oneOf(commits.get('position'), 'identity.signature.commits.position', POSITIONS) ?? 'trailer';
55
+ const prPosition = check.oneOf(prs.get('position'), 'identity.signature.pull_requests.position', POSITIONS) ?? 'last-line';
56
+ if (commitPosition === 'trailer' && !isTrailerTemplate(commitTemplate)) {
57
+ const line = commits.get('position')?.line ?? commits.get('template')?.line ?? signature.get('template')?.line ?? entry?.line ?? 1;
58
+ check.fail(line, 'position "trailer" needs a template git reads as a trailer: "<token>: <value>", the token without spaces');
59
+ }
60
+ const exemptEntry = commits.get('exempt');
61
+ const exempt = exemptEntry ? check.list(exemptEntry, 'identity.signature.commits.exempt') : [{ value: 'merge', line: 0 }];
62
+ for (const item of exempt) {
63
+ if (item.value !== 'merge')
64
+ check.fail(item.line, `exempt takes only "merge" in this version, found "${item.value}"`);
65
+ }
66
+ const prExempt = prs.get('exempt');
67
+ if (prExempt)
68
+ check.fail(prExempt.line, 'pull_requests has no exempt: it applies to commits only');
69
+ const forbidden = readPatterns(identity.get('forbidden'), 'identity.forbidden', check);
70
+ const forbiddenPublic = readPatterns(identity.get('forbidden_public'), 'identity.forbidden_public', check);
71
+ return {
72
+ signature: {
73
+ commits: { template: commitTemplate, position: commitPosition, exempt: exempt.some((item) => item.value === 'merge') ? ['merge'] : [] },
74
+ pullRequests: { template: prTemplate, position: prPosition },
75
+ },
76
+ humans: check.list(identity.get('humans'), 'identity.humans').map((item) => item.value),
77
+ since: check.text(identity.get('since'), 'identity.since') ?? null,
78
+ forbidden: [...DEFAULT_FORBIDDEN, ...forbidden.filter((pattern) => !DEFAULT_FORBIDDEN.includes(pattern))],
79
+ forbiddenPublic,
80
+ };
81
+ }
82
+ function readTemplate(entry, name, check) {
83
+ const template = check.text(entry, name);
84
+ if (template === undefined || !entry)
85
+ return undefined;
86
+ const problem = templateProblem(template);
87
+ if (problem)
88
+ check.fail(entry.value.line, `${name}: ${problem}`);
89
+ return template;
90
+ }
91
+ function readPatterns(entry, name, check) {
92
+ const out = [];
93
+ for (const item of check.list(entry, name)) {
94
+ try {
95
+ new RegExp(item.value);
96
+ out.push(item.value);
97
+ }
98
+ catch (error) {
99
+ check.fail(item.line, `${name}: not a regular expression (${error.message})`);
100
+ }
101
+ }
102
+ return out;
103
+ }
@@ -0,0 +1,9 @@
1
+ import type { Section } from './section.ts';
2
+ /**
3
+ * Every section of the team file, in one order. The owner-only list is generated from this order
4
+ * (`SECTIONS.filter(s => s.owner).map(s => s.name)`), and that order feeds the approval digest,
5
+ * so it is part of the contract: the owner sections appear here exactly as the digest reads them.
6
+ * The validator walks the list running each section once every section it names in `after` has
7
+ * run, which is the order `validate.ts` read them in before the sections were modules.
8
+ */
9
+ export declare const SECTIONS: readonly Section[];
@@ -0,0 +1,43 @@
1
+ import { format } from "./format.js";
2
+ import { project } from "./project.js";
3
+ import { trust } from "./trust.js";
4
+ import { limits } from "./limits.js";
5
+ import { machine } from "./machine.js";
6
+ import { rules } from "./rules.js";
7
+ import { identity } from "./identity.js";
8
+ import { workspace } from "./workspace.js";
9
+ import { coordinator } from "./coordinator.js";
10
+ import { operator } from "./operator.js";
11
+ import { session } from "./session.js";
12
+ import { visibility } from "./visibility.js";
13
+ import { tools } from "./tools.js";
14
+ import { budgets } from "./budgets.js";
15
+ import { watch } from "./watch.js";
16
+ import { watchChecks } from "./watch-checks.js";
17
+ import { seats } from "./seats.js";
18
+ /**
19
+ * Every section of the team file, in one order. The owner-only list is generated from this order
20
+ * (`SECTIONS.filter(s => s.owner).map(s => s.name)`), and that order feeds the approval digest,
21
+ * so it is part of the contract: the owner sections appear here exactly as the digest reads them.
22
+ * The validator walks the list running each section once every section it names in `after` has
23
+ * run, which is the order `validate.ts` read them in before the sections were modules.
24
+ */
25
+ export const SECTIONS = [
26
+ format,
27
+ project,
28
+ trust,
29
+ limits,
30
+ machine,
31
+ rules,
32
+ identity,
33
+ workspace,
34
+ coordinator,
35
+ operator,
36
+ session,
37
+ visibility,
38
+ tools,
39
+ budgets,
40
+ watch,
41
+ watchChecks,
42
+ seats,
43
+ ];
@@ -0,0 +1,4 @@
1
+ import type { YamlEntry } from '../../yaml.ts';
2
+ import type { Check } from '../check.ts';
3
+ import type { DraftSeat } from './seats.ts';
4
+ export declare function readLead(entry: YamlEntry | undefined, field: 'coordinator' | 'operator', seats: DraftSeat[], broken: Set<string>, check: Check): string;
@@ -0,0 +1,18 @@
1
+ export function readLead(entry, field, seats, broken, check) {
2
+ const name = check.required(entry, field, 1);
3
+ if (!name || !entry)
4
+ return '';
5
+ const seat = seats.find((candidate) => candidate.name === name);
6
+ const line = entry.value.line;
7
+ if (!seat) {
8
+ if (!broken.has(name))
9
+ check.fail(line, `${field} "${name}" names no declared seat`);
10
+ }
11
+ else if (seat.count > 1)
12
+ check.fail(line, `${field} "${name}" can't be a seat with count`);
13
+ else if (seat.parked)
14
+ check.fail(line, `${field} "${name}" can't be a parked seat`);
15
+ else if (seat.stopped)
16
+ check.fail(line, `${field} "${name}" can't be a stopped seat`);
17
+ return name;
18
+ }
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const limits: Section;
@@ -0,0 +1,43 @@
1
+ import { valueOf } from "./section.js";
2
+ export const limits = {
3
+ name: 'limits',
4
+ owner: true,
5
+ after: ['seats'],
6
+ validate(entry, ctx) {
7
+ const declared = valueOf(ctx, 'seats').length;
8
+ return readLimits(entry, ctx.check, declared);
9
+ },
10
+ schema: {
11
+ type: 'object',
12
+ additionalProperties: false,
13
+ properties: {
14
+ seats: { type: 'integer', minimum: 1 },
15
+ temporary: { type: 'integer', minimum: 0 },
16
+ vendors: { type: 'object', additionalProperties: { type: 'integer', minimum: 0 } },
17
+ },
18
+ $comment: 'seats defaults to the declared seats plus temporary',
19
+ },
20
+ };
21
+ function readLimits(entry, check, declared) {
22
+ const fields = check.fields(entry?.value, 'limits', ['seats', 'temporary', 'vendors']);
23
+ const temporary = check.whole(fields.get('temporary'), 'limits.temporary', 0) ?? 2;
24
+ const vendors = {};
25
+ const vendorsEntry = fields.get('vendors');
26
+ if (vendorsEntry) {
27
+ const node = vendorsEntry.value;
28
+ if (node.kind !== 'map')
29
+ check.fail(node.line, 'limits.vendors must be a map: a vendor, then its ceiling');
30
+ else {
31
+ for (const vendor of node.entries) {
32
+ const ceiling = check.whole(vendor, `limits.vendors.${vendor.key}`, 0);
33
+ if (ceiling !== undefined)
34
+ vendors[vendor.key] = ceiling;
35
+ }
36
+ }
37
+ }
38
+ return {
39
+ seats: check.whole(fields.get('seats'), 'limits.seats', 1) ?? declared + temporary,
40
+ temporary,
41
+ vendors,
42
+ };
43
+ }
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const machine: Section;
@@ -0,0 +1,38 @@
1
+ import { measureSchema, DURATION, PERCENT, SIZE } from "./units.js";
2
+ export const machine = {
3
+ name: 'machine',
4
+ owner: true,
5
+ after: [],
6
+ validate(entry, ctx) {
7
+ return readMachine(entry, ctx.check);
8
+ },
9
+ schema: {
10
+ type: 'object',
11
+ additionalProperties: false,
12
+ properties: {
13
+ load_start: { type: 'number', exclusiveMinimum: 0 },
14
+ load_max: { type: 'number', exclusiveMinimum: 0 },
15
+ memory_start: measureSchema(PERCENT),
16
+ memory_min: measureSchema(PERCENT),
17
+ disk_min: measureSchema(SIZE),
18
+ swap_free_min: measureSchema(SIZE),
19
+ swap_growth_max: measureSchema(SIZE),
20
+ swap_growth_window: measureSchema(DURATION),
21
+ },
22
+ },
23
+ };
24
+ function readMachine(entry, check) {
25
+ const fields = check.fields(entry?.value, 'machine', [
26
+ 'load_start', 'load_max', 'memory_start', 'memory_min', 'disk_min', 'swap_free_min', 'swap_growth_max', 'swap_growth_window',
27
+ ]);
28
+ return {
29
+ loadStart: check.number(fields.get('load_start'), 'machine.load_start') ?? 3,
30
+ loadMax: check.number(fields.get('load_max'), 'machine.load_max') ?? 6,
31
+ memoryStart: check.measure(fields.get('memory_start'), 'machine.memory_start', PERCENT, '25%') ?? 25,
32
+ memoryMin: check.measure(fields.get('memory_min'), 'machine.memory_min', PERCENT, '15%') ?? 15,
33
+ diskMin: check.measure(fields.get('disk_min'), 'machine.disk_min', SIZE, '10GB') ?? 10e9,
34
+ swapFreeMin: check.measure(fields.get('swap_free_min'), 'machine.swap_free_min', SIZE, '2GB') ?? 2e9,
35
+ swapGrowthMax: check.measure(fields.get('swap_growth_max'), 'machine.swap_growth_max', SIZE, '1GB') ?? 1e9,
36
+ swapGrowthWindow: check.measure(fields.get('swap_growth_window'), 'machine.swap_growth_window', DURATION, '10m') ?? 600,
37
+ };
38
+ }
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const operator: Section;
@@ -0,0 +1,11 @@
1
+ import { valueOf } from "./section.js";
2
+ import { readLead } from "./lead.js";
3
+ export const operator = {
4
+ name: 'operator',
5
+ owner: true,
6
+ after: ['seats'],
7
+ validate(entry, ctx) {
8
+ return readLead(entry, 'operator', valueOf(ctx, 'seats'), ctx.broken, ctx.check);
9
+ },
10
+ schema: { type: 'string', minLength: 1, $comment: 'must name a declared seat: one, without count, neither parked nor stopped' },
11
+ };
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const project: Section;
@@ -0,0 +1,13 @@
1
+ export const project = {
2
+ name: 'project',
3
+ owner: false,
4
+ after: [],
5
+ validate(entry, ctx) {
6
+ const project = ctx.check.required(entry, 'project', 1) ?? '';
7
+ if (project && !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(project)) {
8
+ ctx.check.fail(entry?.line ?? 1, 'project must be a short name: letters, digits, ".", "_" and "-"');
9
+ }
10
+ return project;
11
+ },
12
+ schema: { type: 'string', minLength: 1, pattern: '^[A-Za-z0-9][A-Za-z0-9._-]*$' },
13
+ };
@@ -0,0 +1,2 @@
1
+ import type { Section } from './section.ts';
2
+ export declare const rules: Section;
@@ -0,0 +1,9 @@
1
+ export const rules = {
2
+ name: 'rules',
3
+ owner: true,
4
+ after: [],
5
+ validate(entry, ctx) {
6
+ return ctx.check.list(entry, 'rules').map((item) => item.value);
7
+ },
8
+ schema: { type: 'array', items: { type: 'string', minLength: 1 } },
9
+ };
@@ -0,0 +1,7 @@
1
+ import type { Mode, Seat } from '../types.ts';
2
+ import type { Section } from './section.ts';
3
+ export type DraftSeat = Omit<Seat, 'mode'> & {
4
+ mode?: Mode;
5
+ };
6
+ export declare const seats: Section;
7
+ export declare function defaultLabel(model: string, version: string): string;