@vivswan/github-settings-as-code 2.0.1-main.1083.g04d8d35 → 2.0.1-main.1090.g15ef0c9

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.

Potentially problematic release.


This version of @vivswan/github-settings-as-code might be problematic. Click here for more details.

package/LICENSE.md CHANGED
@@ -557,5 +557,5 @@ software incorporates into what it produces are output. The software's
557
557
  **Use** means anything you do with the software requiring one of your
558
558
  licenses.
559
559
 
560
- <!-- The license text between the BEGIN/END markers is managed by Vivswan/repo-platform and replaced on every sync. Repository-specific license notices (third-party components, differently licensed paths) go below the END marker; they are this repository's own and survive every sync. -->
560
+ <!-- The license text between the BEGIN/END markers is managed by the platform and replaced on every sync. Repository-specific license notices (third-party components, differently licensed paths) go below the END marker; they are this repository's own and survive every sync. -->
561
561
  <!-- END REPO-PLATFORM MANAGED -->
package/lib/pkg/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { C as SNAPSHOT_ONLY_INPUTS, E as parseSnapshotFileConfig, F as concludeMerge, I as concludeRun, Kt as redactRanges, L as failRun, Rt as GithubApi, S as SNAPSHOT_INPUTS, T as parseConfig, U as PRIVATE_REPORT_CHANNELS, Wt as maskRegistry, _ as INPUT_DECLS, d as runSnapshot, f as runSingle, j as readSettingsFile, k as runMulti, o as snapshotRepository, pt as SECTIONS, s as validateSettings, t as runMerge, tt as describeProblem, u as concludeSnapshot, v as MERGE_INPUTS, y as MERGE_ONLY_INPUTS, yt as sectionGrant } from "./src-L_4G-LmP.js";
2
+ import { C as SNAPSHOT_ONLY_INPUTS, E as parseSnapshotFileConfig, F as concludeMerge, I as concludeRun, Kt as redactRanges, L as failRun, Rt as GithubApi, S as SNAPSHOT_INPUTS, T as parseConfig, U as PRIVATE_REPORT_CHANNELS, Wt as maskRegistry, _ as INPUT_DECLS, d as runSnapshot, f as runSingle, j as readSettingsFile, k as runMulti, o as snapshotRepository, pt as SECTIONS, s as validateSettings, t as runMerge, tt as describeProblem, u as concludeSnapshot, v as MERGE_INPUTS, y as MERGE_ONLY_INPUTS, yt as sectionGrant } from "./src-BhuD5Ycj.js";
3
3
  import { appendFileSync, existsSync, mkdirSync, writeFileSync } from "node:fs";
4
4
  import { dirname } from "node:path";
5
5
  import { ResultAsync, err, ok } from "neverthrow";
@@ -1461,6 +1461,8 @@ interface UndeclaredPolicyList<E> {
1461
1461
  _layering?: "merge" | "replace";
1462
1462
  }
1463
1463
  type MustBeNever<T extends never> = T;
1464
+ /** Readonly through every nested object and array; functions pass untouched. The type twin of a deep Object.freeze. */
1465
+ type DeepReadonly<T> = T extends ((...args: never[]) => unknown) ? T : T extends object ? { readonly [P in keyof T]: DeepReadonly<T[P]>; } : T;
1464
1466
  //#endregion
1465
1467
  //#region src/sections/contract/module.d.ts
1466
1468
  type EndpointDict = Readonly<Record<string, EndpointDecl>>;
@@ -2012,12 +2014,15 @@ type Address<Ends extends ListEndpoints> = Ends extends {
2012
2014
  readonly route: infer U extends string;
2013
2015
  };
2014
2016
  } ? SameParams<U, Ends["remove"]["route"]> : Readonly<Record<PathParams<Ends["remove"]["route"]>, string>>;
2015
- /** Declared fields only: an omitted optional stays OUT (never undefined), so it is neither written nor compared. */
2016
- type Write<F extends string> = { readonly [P in F]: string; } & {
2017
+ /**
2018
+ * Declared fields only: an omitted optional stays OUT (never undefined), so it is neither written nor compared.
2019
+ * A section narrows it to pin a folded field's brand on its lens (labels' HexColor).
2020
+ */
2021
+ type ListWrite<F extends string> = { readonly [P in F]: string; } & {
2017
2022
  readonly [key: string]: PlainData;
2018
2023
  };
2019
2024
  /** A live item in the same terms, each field normalized as GitHub stores it. */
2020
- type Comparable<F extends string> = { readonly [P in F]: string; } & Readonly<Record<string, unknown>>;
2025
+ type ListComparable<F extends string> = { readonly [P in F]: string; } & Readonly<Record<string, unknown>>;
2021
2026
  type NoteWording = Pick<Parameters<typeof undeclaredNote>[0], "state" | "add" | "manage">;
2022
2027
  type DriftWording = Pick<Parameters<typeof undeclaredDrift>[1], "state" | "add" | "keep">;
2023
2028
  /** `unpaginated` also drives the derived mock's list handler (test/e2e/mock/list-fragment.ts). */
@@ -2027,7 +2032,11 @@ interface Listing {
2027
2032
  /** GitHub serves the whole list in one response and ignores page params (autolinks), so the page loop is skipped. */
2028
2033
  readonly unpaginated?: true;
2029
2034
  }
2030
- interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints, Live extends object, F extends string> {
2035
+ /**
2036
+ * `Key` is the fold's output: the planner keys every live-versus-declared lookup by it, so an unfolded
2037
+ * name cannot be looked up, and a section's brand (labels' NameKey) survives to `decl`.
2038
+ */
2039
+ interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints, Live extends object, F extends string, Key extends string> {
2031
2040
  readonly key: K;
2032
2041
  readonly permission: SectionPermission;
2033
2042
  readonly undeclaredDefault: UndeclaredPolicy;
@@ -2042,8 +2051,8 @@ interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints,
2042
2051
  readonly identity: {
2043
2052
  /** The write field naming the resource, as the live item carries it ("name", "title"). */
2044
2053
  readonly field: F;
2045
- /** Folds a name to its matching key; omitted when GitHub matches exactly. */
2046
- readonly fold?: (name: string) => string;
2054
+ /** Folds a name to the key GitHub matches it by; `exactName` when GitHub matches exactly. */
2055
+ readonly fold: (name: string) => Key;
2047
2056
  /**
2048
2057
  * Names an entry also answers to (a label's pre-rename `name`), so a live item under one is this
2049
2058
  * entry's, renamed by the update, not undeclared.
@@ -2062,7 +2071,7 @@ interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints,
2062
2071
  readonly address: [Address<Ends>] extends [never] ? never : (live: Live) => Address<Ends>;
2063
2072
  readonly lens: {
2064
2073
  /** The entry in wire terms: the create body, and what a converged live item reads back as. */
2065
- readonly toWrite: (entry: Entry<K>) => Write<F>;
2074
+ readonly toWrite: (entry: Entry<K>) => ListWrite<F>;
2066
2075
  /**
2067
2076
  * A live item in the same terms as toWrite, so the two compare field by field.
2068
2077
  *
@@ -2070,7 +2079,7 @@ interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints,
2070
2079
  * other declared fields -> normalized as GitHub stores them (a color lowercased without "#", a null description as "")
2071
2080
  * every other live field -> kept, so declared passthrough keys compare against what the API echoed
2072
2081
  */
2073
- readonly fromLive: (live: Live) => Comparable<F>;
2082
+ readonly fromLive: (live: Live) => ListComparable<F>;
2074
2083
  /** Per entry field holding a list, the item key to pair by (see DeltaOptions.matchBy); `{}` when none does. */
2075
2084
  readonly matchBy: Readonly<Partial<Record<keyof Entry<K> & string, string>>>;
2076
2085
  };
@@ -2078,7 +2087,7 @@ interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints,
2078
2087
  * The body recreating a drifted item of a resource GitHub cannot edit (no update role), when the
2079
2088
  * write alone would drop a live field the file leaves undeclared (a deploy key's read_only).
2080
2089
  */
2081
- readonly recreate?: "update" extends keyof Ends ? never : (live: Live, write: Write<F>) => Write<F>;
2090
+ readonly recreate?: "update" extends keyof Ends ? never : (live: Live, write: ListWrite<F>) => ListWrite<F>;
2082
2091
  /**
2083
2092
  * Conflicts the identities cannot show, one line each naming the fix; any line fails the section.
2084
2093
  *
@@ -2086,8 +2095,8 @@ interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints,
2086
2095
  * `live` -> runs after the read and before any write (a deploy key's material held by another key)
2087
2096
  */
2088
2097
  readonly conflicts?: {
2089
- readonly declared?: (writes: readonly Write<F>[]) => readonly string[];
2090
- readonly live?: (writes: readonly Write<F>[], live: readonly Comparable<F>[]) => readonly string[];
2098
+ readonly declared?: (writes: readonly ListWrite<F>[]) => readonly string[];
2099
+ readonly live?: (writes: readonly ListWrite<F>[], live: readonly ListComparable<F>[]) => readonly string[];
2091
2100
  };
2092
2101
  readonly prose: {
2093
2102
  /** What apply does to an undeclared live resource, as the note and drift spell it ("DELETE it"). */
@@ -2104,7 +2113,7 @@ interface ListSectionDecl<K extends ListSectionKey, Ends extends ListEndpoints,
2104
2113
  readonly layering?: Pick<KeyedListLayering, "combine">;
2105
2114
  }
2106
2115
  /** The module listSection() mints: SectionModule<K, Ends> at the registry, plus its declaration. */
2107
- interface ListSectionModule<K extends ListSectionKey, Ends extends ListEndpoints, Live extends object, F extends string> {
2116
+ interface ListSectionModule<K extends ListSectionKey, Ends extends ListEndpoints, Live extends object, F extends string, Key extends string> {
2108
2117
  readonly key: K;
2109
2118
  readonly permission: SectionPermission;
2110
2119
  readonly undeclaredDefault: UndeclaredPolicy;
@@ -2115,9 +2124,16 @@ interface ListSectionModule<K extends ListSectionKey, Ends extends ListEndpoints
2115
2124
  readonly plan: (ctx: PlanContext<Ends>, desired: Declared<K>) => Promise<SectionPlan<PlannedOp<Ends>>>;
2116
2125
  readonly snapshot: (ctx: SnapshotContext<Ends>) => Promise<SectionSnapshot<K>>;
2117
2126
  /** The declaration, for the harness derivations (the mock's transformers, the fuzz witness). */
2118
- readonly decl: ListSectionDecl<K, Ends, Live, F>;
2127
+ readonly decl: ListSectionDecl<K, Ends, Live, F, Key>;
2119
2128
  }
2120
2129
  //#endregion
2130
+ //#region src/sections/labels/index.d.ts
2131
+ /** Case-insensitive matching is the section's whole contract; the brand marks a name as already folded. */
2132
+ declare const labelNameKey: unique symbol;
2133
+ type NameKey = string & {
2134
+ readonly [labelNameKey]: true;
2135
+ };
2136
+ //#endregion
2121
2137
  //#region src/sections/environments/endpoints.d.ts
2122
2138
  declare const ENDPOINTS$1: {
2123
2139
  readonly list: {
@@ -3502,7 +3518,7 @@ declare const byKey: {
3502
3518
  name: string;
3503
3519
  color: string;
3504
3520
  description: string | null;
3505
- }, "name">;
3521
+ }, "name", NameKey>;
3506
3522
  rulesets: {
3507
3523
  key: "rulesets";
3508
3524
  undeclaredDefault: "keep";
@@ -5039,7 +5055,7 @@ declare const byKey: {
5039
5055
  [x: string]: unknown;
5040
5056
  id: number;
5041
5057
  key_prefix: string;
5042
- }, "key_prefix">;
5058
+ }, "key_prefix", string>;
5043
5059
  actions: {
5044
5060
  key: "actions";
5045
5061
  undeclaredDefault: "untouched";
@@ -7259,8 +7275,8 @@ declare const byKey: {
7259
7275
  id: number;
7260
7276
  title: string;
7261
7277
  key: string;
7262
- read_only?: boolean | undefined;
7263
- }, "title">;
7278
+ read_only: boolean;
7279
+ }, "title", string>;
7264
7280
  secret_scanning_custom_patterns: {
7265
7281
  key: "secret_scanning_custom_patterns";
7266
7282
  undeclaredDefault: "keep";
@@ -7507,26 +7523,25 @@ type SectionGraphqlKey<K extends SectionKey = SectionKey> = { [S in SectionKey]:
7507
7523
  export declare const SECTIONS: readonly SectionModule[];
7508
7524
  /** The section module for a key (validate.ts reads shape + closedSurface). */
7509
7525
  export declare function sectionModule<K extends SectionKey>(key: K): SectionModule<K>;
7510
- type TaggedEndpoint = EndpointDecl & {
7526
+ type TaggedEndpoint = DeepReadonly<EndpointDecl & {
7511
7527
  readonly section: SectionKey;
7512
7528
  readonly role: string;
7513
- };
7529
+ }>;
7514
7530
  /**
7515
7531
  * The single view the e2e mock's route table and USED_PATHS iterate, keyed by the exact SectionEndpointKey
7516
- * union so an undeclared lookup does not compile.
7517
- *
7518
- * frozen (record, tagged entries, nested statuses/permission) -> they reference the declarations, which must never mutate
7519
- * `sections` injectable -> the scope-free assert is testable; an injected list keeps string keys
7532
+ * union so an undeclared lookup does not compile. Deep-frozen, and the source declaration with it: the
7533
+ * entries reference the declarations, which must never mutate. `sections` is injectable so the scope-free
7534
+ * assert is testable; an injected list keeps string keys.
7520
7535
  */
7521
7536
  export declare function allEndpoints(): Readonly<Record<SectionEndpointKey, TaggedEndpoint>>;
7522
7537
  export declare function allEndpoints(sections: ReadonlyArray<Pick<SectionModule, "key" | "endpoints">>): Readonly<Record<string, TaggedEndpoint>>;
7523
- type TaggedGraphqlOp = GraphqlOpDecl & {
7538
+ type TaggedGraphqlOp = DeepReadonly<GraphqlOpDecl & {
7524
7539
  readonly section: SectionKey;
7525
7540
  readonly role: string;
7526
- };
7541
+ }>;
7527
7542
  /**
7528
7543
  * The allEndpoints() sibling for the mock's dispatch table, the coverage tripwire, and the fault-key
7529
- * universe; frozen for the same reason. Operation NAMES must be globally unique (the wire dispatch key),
7544
+ * universe; deep-frozen for the same reason. Operation NAMES must be globally unique (the wire dispatch key),
7530
7545
  * and a role never collides with a REST role in the same section (fault directives share one "section.role" key space).
7531
7546
  */
7532
7547
  export declare function allGraphqlOps(): Readonly<Record<SectionGraphqlKey, TaggedGraphqlOp>>;
package/lib/pkg/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import { $ as renderSnapshotYaml, A as parseSettingsDoc, At as parseReposInput, B as planRedaction, Bt as SECRET_TRANSPORT_WITHHELD, C as SNAPSHOT_ONLY_INPUTS, Ct as endpointMethod, D as DEFAULT_SETTINGS_FILE, Dt as SECTION_KEYS, E as parseSnapshotFileConfig, Et as PROBOT_PARITY_KEYS, F as concludeMerge, Ft as VISIBILITY_FILTERS, G as openReportChannel, Gt as prefixedIo, H as toPublicView, Ht as isRateLimitError, I as concludeRun, It as discoverRepos, J as MARKER_LABEL_CONFIG, Jt as resolveCentralTargets, K as ISSUE_TITLE, Kt as redactRanges, L as failRun, Lt as DEFAULT_API_VERSION, M as createVisibilityResolver, Mt as ARCHIVED_FILTERS, N as getRepoFile, Nt as DEFAULT_DISCOVERY_FILTERS, O as resolveTargets, Ot as SettingsFile, P as MERGE_RESULT, Pt as FORKS_FILTERS, Q as parseRecipient, R as PRIVATE_REPOS_POLICIES, Rt as GithubApi, S as SNAPSHOT_INPUTS, St as grantFor, T as parseConfig, Tt as DOCUMENT_DIRECTIVE_KEYS, U as PRIVATE_REPORT_CHANNELS, Ut as collectingIo, V as publicDetail, Vt as isPermissionError, W as applyMarkerInjection, Wt as maskRegistry, X as deliverArtifactReport, Xt as parseRepoSlug, Y as composeReport, Yt as dedupeTargets, Z as encryptReport, _ as INPUT_DECLS, _t as denialPosture, a as snapshotRepositories, at as preflightProbe, b as MERGE_REJECTED_INPUTS, bt as sectionOperations, c as SNAPSHOT_RESULTS, ct as validateSettingsDoc, d as runSnapshot, dt as mergeLayers, et as RERUN_ADVICE, f as runSingle, ft as stripNulls, g as FILTER_INPUTS, gt as sectionModule, h as DEFAULT_PRIVATE_REPOS, ht as allGraphqlOps, i as renderMergedYaml, it as REPO_RESULTS, j as readSettingsFile, jt as AFFILIATIONS, k as runMulti, kt as UNDECLARED_POLICY_SECTIONS, l as SNAPSHOT_SCHEMA_URL, lt as worstOf, m as readLayerFiles, mt as allEndpoints, n as applyRepository, nt as quoteList, o as snapshotRepository, ot as runForRepo, p as foldLayers, pt as SECTIONS, q as MARKER_LABEL, qt as silentIo, r as checkRepository, rt as SectionSelection, s as validateSettings, st as skippedSectionKeys, t as runMerge, tt as describeProblem, u as concludeSnapshot, ut as describeOptOut, v as MERGE_INPUTS, vt as readGating, w as SNAPSHOT_REJECTED_INPUTS, wt as endpointPath, x as MODES, xt as writeGatedReads, y as MERGE_ONLY_INPUTS, yt as sectionGrant, z as capturingIo, zt as SECRET_RESPONSE_WITHHELD } from "./src-L_4G-LmP.js";
1
+ import { $ as renderSnapshotYaml, A as parseSettingsDoc, At as parseReposInput, B as planRedaction, Bt as SECRET_TRANSPORT_WITHHELD, C as SNAPSHOT_ONLY_INPUTS, Ct as endpointMethod, D as DEFAULT_SETTINGS_FILE, Dt as SECTION_KEYS, E as parseSnapshotFileConfig, Et as PROBOT_PARITY_KEYS, F as concludeMerge, Ft as VISIBILITY_FILTERS, G as openReportChannel, Gt as prefixedIo, H as toPublicView, Ht as isRateLimitError, I as concludeRun, It as discoverRepos, J as MARKER_LABEL_CONFIG, Jt as resolveCentralTargets, K as ISSUE_TITLE, Kt as redactRanges, L as failRun, Lt as DEFAULT_API_VERSION, M as createVisibilityResolver, Mt as ARCHIVED_FILTERS, N as getRepoFile, Nt as DEFAULT_DISCOVERY_FILTERS, O as resolveTargets, Ot as SettingsFile, P as MERGE_RESULT, Pt as FORKS_FILTERS, Q as parseRecipient, R as PRIVATE_REPOS_POLICIES, Rt as GithubApi, S as SNAPSHOT_INPUTS, St as grantFor, T as parseConfig, Tt as DOCUMENT_DIRECTIVE_KEYS, U as PRIVATE_REPORT_CHANNELS, Ut as collectingIo, V as publicDetail, Vt as isPermissionError, W as applyMarkerInjection, Wt as maskRegistry, X as deliverArtifactReport, Xt as parseRepoSlug, Y as composeReport, Yt as dedupeTargets, Z as encryptReport, _ as INPUT_DECLS, _t as denialPosture, a as snapshotRepositories, at as preflightProbe, b as MERGE_REJECTED_INPUTS, bt as sectionOperations, c as SNAPSHOT_RESULTS, ct as validateSettingsDoc, d as runSnapshot, dt as mergeLayers, et as RERUN_ADVICE, f as runSingle, ft as stripNulls, g as FILTER_INPUTS, gt as sectionModule, h as DEFAULT_PRIVATE_REPOS, ht as allGraphqlOps, i as renderMergedYaml, it as REPO_RESULTS, j as readSettingsFile, jt as AFFILIATIONS, k as runMulti, kt as UNDECLARED_POLICY_SECTIONS, l as SNAPSHOT_SCHEMA_URL, lt as worstOf, m as readLayerFiles, mt as allEndpoints, n as applyRepository, nt as quoteList, o as snapshotRepository, ot as runForRepo, p as foldLayers, pt as SECTIONS, q as MARKER_LABEL, qt as silentIo, r as checkRepository, rt as SectionSelection, s as validateSettings, st as skippedSectionKeys, t as runMerge, tt as describeProblem, u as concludeSnapshot, ut as describeOptOut, v as MERGE_INPUTS, vt as readGating, w as SNAPSHOT_REJECTED_INPUTS, wt as endpointPath, x as MODES, xt as writeGatedReads, y as MERGE_ONLY_INPUTS, yt as sectionGrant, z as capturingIo, zt as SECRET_RESPONSE_WITHHELD } from "./src-BhuD5Ycj.js";
2
2
  export { AFFILIATIONS, ARCHIVED_FILTERS, DEFAULT_API_VERSION, DEFAULT_DISCOVERY_FILTERS, DEFAULT_PRIVATE_REPOS, DEFAULT_SETTINGS_FILE, DOCUMENT_DIRECTIVE_KEYS, FILTER_INPUTS, FORKS_FILTERS, GithubApi, INPUT_DECLS, ISSUE_TITLE, MARKER_LABEL, MARKER_LABEL_CONFIG, MERGE_INPUTS, MERGE_ONLY_INPUTS, MERGE_REJECTED_INPUTS, MERGE_RESULT, MODES, PRIVATE_REPORT_CHANNELS, PRIVATE_REPOS_POLICIES, PROBOT_PARITY_KEYS, REPO_RESULTS, RERUN_ADVICE, SECRET_RESPONSE_WITHHELD, SECRET_TRANSPORT_WITHHELD, SECTIONS, SECTION_KEYS, SNAPSHOT_INPUTS, SNAPSHOT_ONLY_INPUTS, SNAPSHOT_REJECTED_INPUTS, SNAPSHOT_RESULTS, SNAPSHOT_SCHEMA_URL, SectionSelection, SettingsFile, UNDECLARED_POLICY_SECTIONS, VISIBILITY_FILTERS, allEndpoints, allGraphqlOps, applyMarkerInjection, applyRepository, capturingIo, checkRepository, collectingIo, composeReport, concludeMerge, concludeRun, concludeSnapshot, createVisibilityResolver, dedupeTargets, deliverArtifactReport, denialPosture, describeOptOut, describeProblem, discoverRepos, encryptReport, endpointMethod, endpointPath, failRun, foldLayers, getRepoFile, grantFor, isPermissionError, isRateLimitError, maskRegistry, mergeLayers, openReportChannel, parseConfig, parseRecipient, parseRepoSlug, parseReposInput, parseSettingsDoc, parseSnapshotFileConfig, planRedaction, prefixedIo, preflightProbe, publicDetail, quoteList, readGating, readLayerFiles, readSettingsFile, redactRanges, renderMergedYaml, renderSnapshotYaml, resolveCentralTargets, resolveTargets, runForRepo, runMerge, runMulti, runSingle, runSnapshot, sectionGrant, sectionModule, sectionOperations, silentIo, skippedSectionKeys, snapshotRepositories, snapshotRepository, stripNulls, toPublicView, validateSettings, validateSettingsDoc, worstOf, writeGatedReads };
@@ -3609,8 +3609,8 @@ const LiveVariable = z.looseObject({
3609
3609
  name: z.string(),
3610
3610
  value: z.string()
3611
3611
  });
3612
- function missingVariableDrift(scope, label) {
3613
- return `${label}: missing - declared in the settings file but not on ${scope.home ?? "the repo"}; apply will create it`;
3612
+ function missingVariableDrift(label) {
3613
+ return `${label}: missing - declared in the settings file but not on the repo; apply will create it`;
3614
3614
  }
3615
3615
  function valueDriftLine(label, declared, live) {
3616
3616
  return `${label}.value: declared ${JSON.stringify(declared)} != live ${JSON.stringify(live)}; apply will set the declared value`;
@@ -3618,7 +3618,7 @@ function valueDriftLine(label, declared, live) {
3618
3618
  function undeclaredVariableNote(scope, liveName) {
3619
3619
  return undeclaredNote({
3620
3620
  subject: `${scope.noun} "${liveName}"`,
3621
- state: `exists on ${scope.keepHome ?? "the repo"} but is not declared`,
3621
+ state: "exists on the repo but is not declared",
3622
3622
  action: "DELETE it"
3623
3623
  });
3624
3624
  }
@@ -3635,8 +3635,6 @@ function liveVariablesByKey(live) {
3635
3635
  }
3636
3636
  async function planVariables$1(scope, opts) {
3637
3637
  const { entries, policy, defaultPolicy } = opts;
3638
- const changeSuffix = scope.changeSuffix ?? "";
3639
- const removeSuffix = scope.removeSuffix ?? "";
3640
3638
  const plan = {
3641
3639
  ops: [],
3642
3640
  notes: [],
@@ -3650,14 +3648,13 @@ async function planVariables$1(scope, opts) {
3650
3648
  const { name: _name, value: _value, ...extraKeys } = variable;
3651
3649
  if (!existing) {
3652
3650
  plan.ops.push(scope.create({
3653
- name: variable.name,
3654
3651
  payload: {
3655
3652
  name: variable.name,
3656
3653
  value: variable.value,
3657
3654
  ...extraKeys
3658
3655
  },
3659
- drift: [missingVariableDrift(scope, label)],
3660
- change: `created ${scope.noun} "${variable.name}"${changeSuffix}`
3656
+ drift: [missingVariableDrift(label)],
3657
+ change: `created ${scope.noun} "${variable.name}"`
3661
3658
  }));
3662
3659
  continue;
3663
3660
  }
@@ -3666,16 +3663,13 @@ async function planVariables$1(scope, opts) {
3666
3663
  const phantom = phantomKeys(extraKeys, existing);
3667
3664
  if (phantom.length > 0) plan.notes.push(phantomNote(label, phantom, "variable", "this update will re-run"));
3668
3665
  plan.ops.push(scope.update({
3669
- names: {
3670
- declared: variable.name,
3671
- live: existing.name
3672
- },
3666
+ liveName: existing.name,
3673
3667
  payload: {
3674
3668
  value: variable.value,
3675
3669
  ...extraKeys
3676
3670
  },
3677
3671
  drift: [first, ...rest],
3678
- change: `updated ${scope.noun} "${variable.name}"${changeSuffix}`
3672
+ change: `updated ${scope.noun} "${variable.name}"`
3679
3673
  }));
3680
3674
  }
3681
3675
  for (const variable of liveByKey.values()) {
@@ -3684,7 +3678,7 @@ async function planVariables$1(scope, opts) {
3684
3678
  else plan.ops.push(scope.remove({
3685
3679
  name: variable.name,
3686
3680
  drift: [undeclaredVariableDrift(scope, defaultPolicy, variable.name)],
3687
- change: `DELETED undeclared ${scope.noun} "${variable.name}"${removeSuffix}`
3681
+ change: `DELETED undeclared ${scope.noun} "${variable.name}"`
3688
3682
  }));
3689
3683
  }
3690
3684
  return plan;
@@ -3760,7 +3754,7 @@ function repoVariablesSection(family) {
3760
3754
  }),
3761
3755
  update: (write) => ({
3762
3756
  role: "update",
3763
- params: { name: write.names.live },
3757
+ params: { name: write.liveName },
3764
3758
  payload: write.payload,
3765
3759
  drift: write.drift,
3766
3760
  change: write.change
@@ -3841,6 +3835,10 @@ const agentsVariablesSection = repoVariablesSection({
3841
3835
  function updateRole(endpoints) {
3842
3836
  return "update" in endpoints ? endpoints.update : void 0;
3843
3837
  }
3838
+ /** The fold of a section GitHub matches exactly: the key IS the name. */
3839
+ function exactName(name) {
3840
+ return name;
3841
+ }
3844
3842
  const UPDATE_REMEDIES = {
3845
3843
  value: "; apply will set the declared value",
3846
3844
  rename: "apply will rename it",
@@ -3857,7 +3855,7 @@ const RECREATE_REMEDIES = {
3857
3855
  * which the merge refuses and a validated entry never is.
3858
3856
  */
3859
3857
  function identityClaims(identity, entry) {
3860
- const { field, renameKey, fold = (name) => name } = identity;
3858
+ const { field, renameKey, fold } = identity;
3861
3859
  const names = [(renameKey === void 0 ? void 0 : entry[renameKey]) ?? entry[field], ...identity.aliases?.(entry) ?? []];
3862
3860
  if (!names.every((name) => typeof name === "string")) return null;
3863
3861
  return [...new Set(names.map(fold))];
@@ -3895,7 +3893,7 @@ async function readList(decl, ctx) {
3895
3893
  }
3896
3894
  async function planList(decl, section, ctx, declared) {
3897
3895
  const { key, noun, identity, lens, prose, endpoints } = decl;
3898
- const fold = identity.fold ?? ((name) => name);
3896
+ const { fold } = identity;
3899
3897
  const update = updateRole(endpoints);
3900
3898
  const remedies = update === void 0 ? RECREATE_REMEDIES : UPDATE_REMEDIES;
3901
3899
  const defaultPolicy = defaultUndeclaredPolicy(section);
@@ -4019,10 +4017,9 @@ async function snapshotList(decl, section, ctx) {
4019
4017
  value: void 0,
4020
4018
  notes: []
4021
4019
  };
4022
- const fold = decl.identity.fold ?? ((name) => name);
4023
4020
  const comparable = live.map((item) => decl.lens.fromLive(item));
4024
4021
  const nameOfItem = (item) => nameOf(item, decl.identity.field);
4025
- rejectLiveDuplicates(section, decl.noun, comparable, (item) => fold(nameOfItem(item)), nameOfItem);
4022
+ rejectLiveDuplicates(section, decl.noun, comparable, (item) => decl.identity.fold(nameOfItem(item)), nameOfItem);
4026
4023
  return {
4027
4024
  value: knobbedSnapshot(section, comparable.map((item) => projectOntoSchema(decl.entry, item))),
4028
4025
  notes: []
@@ -4083,7 +4080,10 @@ const autolinksSection = listSection({
4083
4080
  }
4084
4081
  },
4085
4082
  listing: { unpaginated: true },
4086
- identity: { field: "key_prefix" },
4083
+ identity: {
4084
+ field: "key_prefix",
4085
+ fold: exactName
4086
+ },
4087
4087
  address: (live) => ({ autolink_id: String(live.id) }),
4088
4088
  lens: {
4089
4089
  toWrite: ({ key_prefix, url_template, is_alphanumeric, ...passthrough }) => ({
@@ -5608,7 +5608,7 @@ const LiveDeployKey = z.looseObject({
5608
5608
  id: z.number(),
5609
5609
  title: z.string(),
5610
5610
  key: z.string(),
5611
- read_only: z.boolean().optional()
5611
+ read_only: z.boolean()
5612
5612
  });
5613
5613
  const ENDPOINTS$11 = {
5614
5614
  list: {
@@ -5652,7 +5652,10 @@ const deployKeysSection = listSection({
5652
5652
  entry: DeployKeyConfig,
5653
5653
  live: LiveDeployKey,
5654
5654
  endpoints: ENDPOINTS$11,
5655
- identity: { field: "title" },
5655
+ identity: {
5656
+ field: "title",
5657
+ fold: exactName
5658
+ },
5656
5659
  address: (live) => ({ key_id: String(live.id) }),
5657
5660
  lens: {
5658
5661
  toWrite: ({ title, key, read_only, ...passthrough }) => ({
@@ -5663,13 +5666,20 @@ const deployKeysSection = listSection({
5663
5666
  }),
5664
5667
  fromLive: (live) => ({
5665
5668
  ...live,
5666
- key: liveMaterial(live),
5667
- read_only: live.read_only ?? false
5669
+ key: liveMaterial(live)
5668
5670
  }),
5669
5671
  matchBy: {}
5670
5672
  },
5673
+ /**
5674
+ * GitHub creates a key READ/WRITE when the body omits read_only, and this file does not manage an
5675
+ * undeclared flag, so the flag reaches a create body only from a source that holds it:
5676
+ *
5677
+ * declared -> the declared value, on a create and a recreate alike (the write is spread last)
5678
+ * undeclared, create -> omitted: GitHub's default, never compared afterwards
5679
+ * undeclared, recreate -> the LIVE flag re-sent, so rotating a read-only key never widens its access
5680
+ */
5671
5681
  recreate: (live, write) => ({
5672
- read_only: live.read_only ?? false,
5682
+ read_only: live.read_only,
5673
5683
  ...write
5674
5684
  }),
5675
5685
  conflicts: {
@@ -8629,19 +8639,25 @@ function sectionModule(key) {
8629
8639
  function assertScopeFree(kind, value) {
8630
8640
  if (value.includes(":")) throw new Error(`BUG: ${kind} "${value}" contains ":", which the "section.role" key space reserves for a future scope prefix ("<scope>:<section>.<role>"); rename it without a colon`);
8631
8641
  }
8642
+ /**
8643
+ * Freezes in place through every nested object and array, so the declarations the tagged views share
8644
+ * cannot mutate under any consumer; functions are left as they are (nothing reads their properties).
8645
+ */
8646
+ function deepFreeze(value) {
8647
+ if (typeof value === "object" && value !== null) {
8648
+ Object.freeze(value);
8649
+ for (const child of Object.values(value)) deepFreeze(child);
8650
+ }
8651
+ return value;
8652
+ }
8632
8653
  function allEndpoints(sections = SECTIONS) {
8633
8654
  const out = {};
8634
8655
  for (const section of sections) {
8635
8656
  assertScopeFree("section key", section.key);
8636
8657
  for (const [role, endpoint] of Object.entries(section.endpoints)) {
8637
8658
  assertScopeFree("role", role);
8638
- Object.freeze(endpoint.statuses);
8639
- if (endpoint.permission && typeof endpoint.permission === "object") {
8640
- Object.freeze(endpoint.permission);
8641
- Object.freeze(endpoint.permission.repo);
8642
- }
8643
- out[`${section.key}.${role}`] = Object.freeze({
8644
- ...endpoint,
8659
+ out[`${section.key}.${role}`] = deepFreeze({
8660
+ ...deepFreeze(endpoint),
8645
8661
  section: section.key,
8646
8662
  role
8647
8663
  });
@@ -8661,13 +8677,8 @@ function allGraphqlOps(sections = SECTIONS) {
8661
8677
  const holder = byName.get(op.name);
8662
8678
  if (holder !== void 0) throw new Error(`BUG: GraphQL operation name "${op.name}" is declared by both ${holder} and ${key}; operation names are the wire dispatch key and must be globally unique`);
8663
8679
  byName.set(op.name, key);
8664
- Object.freeze(op.outcomes);
8665
- if (op.permission && typeof op.permission === "object") {
8666
- Object.freeze(op.permission);
8667
- Object.freeze(op.permission.repo);
8668
- }
8669
- out[key] = Object.freeze({
8670
- ...op,
8680
+ out[key] = deepFreeze({
8681
+ ...deepFreeze(op),
8671
8682
  section: section.key,
8672
8683
  role
8673
8684
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vivswan/github-settings-as-code",
3
- "version": "2.0.1-main.1083.g04d8d35",
3
+ "version": "2.0.1-main.1090.g15ef0c9",
4
4
  "description": "GitHub Action applying declarative repository settings: rulesets, labels, branch protection, and more.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "type": "module",