@davidvornholt/standards 0.26.0 → 0.26.2

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/README.md CHANGED
@@ -60,7 +60,7 @@ Existing project-owned files are kept. Synced files are replaced. `init` refuses
60
60
  ```sh
61
61
  bun standards sync --dry-run
62
62
  bun standards sync
63
- bun standards sync --ref v0.26.0
63
+ bun standards sync --ref v0.26.1
64
64
  bun standards sync --from ../standards
65
65
  ```
66
66
 
@@ -97,7 +97,7 @@ bun standards structure --profile source
97
97
 
98
98
  `doctor` checks project-owned integration points such as `biome.jsonc`, `AGENTS.local.md`, package scripts, sync policy, settings overlays, and Dependabot inputs.
99
99
 
100
- `structure` checks the exact Bun declaration, workspace scripts, internal dependency versions, package exports, TypeScript inheritance, accessibility wiring, workspace READMEs, and CI secret structure. The `source` profile records the few intentional differences in this repository itself.
100
+ `structure` checks the exact Bun declaration, workspace scripts, internal dependency versions, package exports, TypeScript inheritance, accessibility wiring, and CI secret structure. The `source` profile records the few intentional differences in this repository itself.
101
101
 
102
102
  ## dependabot
103
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@davidvornholt/standards",
3
- "version": "0.26.0",
3
+ "version": "0.26.2",
4
4
  "description": "Bootstrap, synchronize, and validate davidvornholt/standards consumers",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -166,7 +166,6 @@
166
166
  "src/structure-check.ts",
167
167
  "src/structure-bun-version.ts",
168
168
  "src/structure-profile.ts",
169
- "src/structure-readme.ts",
170
169
  "src/structure-secret-document.ts",
171
170
  "src/structure-script.ts",
172
171
  "src/structure-secrets.ts",
@@ -184,17 +183,17 @@
184
183
  },
185
184
  "devDependencies": {
186
185
  "@davidvornholt/typescript-config": "0.0.0",
187
- "@types/bun": "^1.4.0",
186
+ "@types/bun": "^1.4.2",
188
187
  "sigstore": "5.0.0",
189
188
  "typescript": "7.0.2"
190
189
  },
191
190
  "engines": {
192
- "bun": ">=1.4.0"
191
+ "bun": ">=1.4.2"
193
192
  },
194
193
  "publishConfig": {
195
194
  "access": "public"
196
195
  },
197
196
  "dependencies": {
198
- "yaml": "^2.9.0"
197
+ "yaml": "^2.9.1"
199
198
  }
200
199
  }
package/src/cli.ts CHANGED
@@ -1189,7 +1189,7 @@ const parseArgs = (argv: ReadonlyArray<string>): CliOptions => {
1189
1189
 
1190
1190
  // Canonical monorepo structure gate: workspace and root script shapes,
1191
1191
  // internal versioning, `exports`, tsconfig inheritance, a11y wiring,
1192
- // repo-owned workspace READMEs, and the undecrypted CI secrets contract
1192
+ // and the undecrypted CI secrets contract
1193
1193
  // (SOPS shape mirroring plus the keys the synced workflows require).
1194
1194
  // The `source` profile instead pins the standards template repository's own
1195
1195
  // deliberate exceptions to the consumer contract.
@@ -3,6 +3,8 @@
3
3
  // itself, lists and mints scoped tokens, and revokes them. Token values
4
4
  // returned by create flow only into SOPS writers,
5
5
  // never stdout.
6
+ // User-owned tokens are intentionally outside this API and the bootstrap
7
+ // credential's authority; they must be managed separately in Cloudflare.
6
8
 
7
9
  import {
8
10
  type CfResult,
@@ -57,6 +57,8 @@ export const loadOwnedGithubStore = async (
57
57
  return { ok: true, value: migrated };
58
58
  };
59
59
 
60
+ // Private Apps belong to one owner. Selecting by repository owner keeps each
61
+ // owner's credentials separate; installation access is verified before export.
60
62
  export const selectGithubAppForRepo = (
61
63
  apps: ReadonlyArray<GithubBrokerApp>,
62
64
  repo: string,
package/src/creds-plan.ts CHANGED
@@ -7,6 +7,8 @@
7
7
  // tokens it minted for another repository, are reported
8
8
  // (creds-plan-unmanaged.ts) but never mutated. Execution lives in
9
9
  // creds-plan-run.ts.
10
+ // Live policy is preserved, not compared with a separate desired-policy manifest.
11
+ // Adding policy-drift enforcement would require a new source of truth.
10
12
 
11
13
  import { cloudflareExpiresOn } from './creds-cloudflare-expiry';
12
14
  import { groupByIntersectingFootprint } from './creds-plan-groups';
@@ -24,6 +24,9 @@ export type BrokerStore = {
24
24
  readonly cloudflare: ReadonlyArray<CloudflareBrokerAccount>;
25
25
  };
26
26
  export const EMPTY_BROKER_STORE: BrokerStore = { github: [], cloudflare: [] };
27
+ // This machine-global store trusts the local account, like its age private key.
28
+ // Encrypting it with a colocated key would not protect against that account.
29
+ // Repository secrets still belong in SOPS; this store stays outside repositories.
27
30
  const OWNER_ONLY_FILE_MODE = 0o600;
28
31
  const OWNER_ONLY_DIR_MODE = 0o700;
29
32
  const FILE_MODE_MODULUS = 0o1000;
@@ -22,6 +22,8 @@ export type ResolvedDevEnv = {
22
22
  readonly problems: ReadonlyArray<string>;
23
23
  };
24
24
 
25
+ // Each variable independently selects a complete pair and then one part.
26
+ // Variable names and neighboring references do not imply a shared credential.
25
27
  const lookupReference = (
26
28
  document: unknown,
27
29
  reference: BrokeredS3Reference,
@@ -120,6 +120,8 @@ const BYPASS_ACTOR_PROBE = 1;
120
120
  // `totalCount` means the connection is being filtered rather than merely
121
121
  // redacted, so it supplies no count; a null *element* is the documented
122
122
  // identity-withholding shape, so only the length is checked.
123
+ // Probes with restricted tokens confirmed that hidden actors still count:
124
+ // https://github.com/davidvornholt/standards/pull/176
123
125
  const countedRuleset = (node: unknown): readonly [number, number] | null => {
124
126
  if (!isRecord(node) || typeof node.databaseId !== 'number') {
125
127
  return null;
@@ -163,6 +163,9 @@ export const runCodex = async (
163
163
  {
164
164
  detached: true,
165
165
  stdio: ['ignore', 'ignore', 'pipe'],
166
+ // Approved runs share the host service identity and its GitHub token.
167
+ // Approval and write revalidation constrain the workflow; they do not
168
+ // isolate credentials. This assumes trusted, maintainer-approved work.
166
169
  env: { ...process.env },
167
170
  },
168
171
  );
@@ -1,6 +1,7 @@
1
1
  import { existsSync } from 'node:fs';
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import { isAbsolute, join } from 'node:path';
4
+ import { isContainedPath } from './contained-path';
4
5
  import { isRecord } from './github-settings-parse';
5
6
  import { readJsonFile } from './json-file';
6
7
  import { collectBunVersionProblems } from './structure-bun-version';
@@ -9,7 +10,6 @@ import {
9
10
  rootScriptExpectations,
10
11
  type StructureProfile,
11
12
  } from './structure-profile';
12
- import { collectWorkspaceReadmeProblems } from './structure-readme';
13
13
  import { filteredTurboAliasProblem, hasSafeCommands } from './structure-script';
14
14
  import { collectCiSecretsProblems } from './structure-secrets';
15
15
  import { inspectWorkspace, type Workspace } from './structure-workspace';
@@ -106,6 +106,12 @@ const loadWorkspace = async (
106
106
  if (!existsSync(path)) {
107
107
  return { workspace: null, problem: null };
108
108
  }
109
+ if (!isContainedPath(consumer, `${rel}/package.json`, 'file')) {
110
+ return {
111
+ workspace: null,
112
+ problem: `${rel}: package.json must be a contained regular file; symlinked paths are not allowed`,
113
+ };
114
+ }
109
115
  const manifest = await readJsonFile(path);
110
116
  if (manifest === null) {
111
117
  return {
@@ -174,16 +180,9 @@ export const collectStructureProblems = async (
174
180
  .map((ws) => ws.manifest.name)
175
181
  .filter((name): name is string => typeof name === 'string'),
176
182
  );
177
- const [inspections, readmeProblems] = await Promise.all([
178
- Promise.all(
179
- workspaces.map((ws) => inspectWorkspace(ws, workspaceNames, profile)),
180
- ),
181
- collectWorkspaceReadmeProblems(
182
- consumer,
183
- profile,
184
- workspaces.map((ws) => ws.rel),
185
- ),
186
- ]);
183
+ const inspections = await Promise.all(
184
+ workspaces.map((ws) => inspectWorkspace(ws, workspaceNames, profile)),
185
+ );
187
186
  const requireA11y = inspections.some((i) => i.hasA11ySuite);
188
187
  return [
189
188
  ...declaration.problems,
@@ -193,7 +192,6 @@ export const collectStructureProblems = async (
193
192
  ...collectBunVersionProblems(root),
194
193
  ...inspectRootScripts(root, profile, requireA11y),
195
194
  ...inspections.flatMap((i) => [...i.problems]),
196
- ...readmeProblems,
197
195
  ...secretsProblems,
198
196
  ];
199
197
  };
@@ -1,85 +0,0 @@
1
- import { lstat, readFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import { isContainedPath } from './contained-path';
4
- import { readJsonFile } from './json-file';
5
- import type { StructureProfile } from './structure-profile';
6
-
7
- const SYNC_MANIFEST = 'sync-standards.json';
8
- const TRAILING_SLASHES = /\/+$/u;
9
- const MANIFEST_REQUIREMENT = `${SYNC_MANIFEST}: must contain a JSON object with a "paths" array of strings; the structure gate reads it to tell canonical workspaces from repo-owned ones`;
10
- const MANIFEST_CONTAINMENT = `${SYNC_MANIFEST}: must be a contained regular file; symlinked paths are not allowed`;
11
-
12
- type CanonicalRels = {
13
- readonly rels: ReadonlySet<string>;
14
- readonly problem: string | null;
15
- };
16
-
17
- // Canonical workspaces are synced from the standards template and cannot be
18
- // fixed in a consumer checkout, so the consumer profile exempts them from
19
- // repo-owned documentation rules. The source repository owns those workspaces,
20
- // so the source profile exempts nothing.
21
- const canonicalWorkspaceRels = async (
22
- consumer: string,
23
- profile: StructureProfile,
24
- ): Promise<CanonicalRels> => {
25
- if (profile === 'source') {
26
- return { rels: new Set(), problem: null };
27
- }
28
- const manifestPath = join(consumer, SYNC_MANIFEST);
29
- const manifestInfo = await lstat(manifestPath).catch(() => null);
30
- if (
31
- manifestInfo !== null &&
32
- !isContainedPath(consumer, SYNC_MANIFEST, 'file')
33
- ) {
34
- return { rels: new Set(), problem: MANIFEST_CONTAINMENT };
35
- }
36
- const manifest = await readJsonFile(manifestPath);
37
- const paths = manifest?.paths;
38
- if (
39
- !(
40
- Array.isArray(paths) &&
41
- paths.every((path): path is string => typeof path === 'string')
42
- )
43
- ) {
44
- return { rels: new Set(), problem: MANIFEST_REQUIREMENT };
45
- }
46
- return {
47
- rels: new Set(paths.map((path) => path.replace(TRAILING_SLASHES, ''))),
48
- problem: null,
49
- };
50
- };
51
-
52
- const readmeProblem = async (
53
- consumer: string,
54
- rel: string,
55
- ): Promise<string | null> => {
56
- const readmeRel = `${rel}/README.md`;
57
- const readmePath = join(consumer, readmeRel);
58
- const info = await lstat(readmePath).catch(() => null);
59
- if (info !== null && !isContainedPath(consumer, readmeRel, 'file')) {
60
- return `${rel}: README.md must be a contained regular file; symlinked paths are not allowed`;
61
- }
62
- const raw = await readFile(readmePath, 'utf8').catch(() => null);
63
- return raw !== null && raw.trim() !== ''
64
- ? null
65
- : `${rel}: repo-owned workspace must have a non-empty README.md`;
66
- };
67
-
68
- export const collectWorkspaceReadmeProblems = async (
69
- consumer: string,
70
- profile: StructureProfile,
71
- rels: ReadonlyArray<string>,
72
- ): Promise<ReadonlyArray<string>> => {
73
- const canonical = await canonicalWorkspaceRels(consumer, profile);
74
- if (canonical.problem !== null) {
75
- // Without the manifest the gate cannot tell canonical workspaces from
76
- // repo-owned ones, so it fails on the manifest instead of guessing.
77
- return [canonical.problem];
78
- }
79
- const problems = await Promise.all(
80
- rels
81
- .filter((rel) => !canonical.rels.has(rel))
82
- .map((rel) => readmeProblem(consumer, rel)),
83
- );
84
- return problems.filter((problem): problem is string => problem !== null);
85
- };