@@ -35,13 +35,36 @@ export const CLAUDE_MD = 'CLAUDE.md';
3535// The whole line (and its trailing newline) is removed wherever it appears.
3636const CORPUS_IMPORT_RE = / ^ .* @ \. c l a u d i n i t e \/ s h a r e d \/ C L A U D E \. m d .* \n ? / m;
3737
38+ // The repo Actions secrets its scheduled tasks declare via `required_secrets`,
39+ // deduped and sorted. Async because task discovery is; pure otherwise.
40+ export async function declaredSecrets ( root , config ) {
41+ const { discoverTasks } = await import ( './discover.mjs' ) ;
42+ const { tasks } = await discoverTasks ( root , config ) ;
43+ return [ ...new Set ( tasks . flatMap ( ( t ) => t . decl ?. required_secrets ?? [ ] ) ) ] . sort ( ) ;
44+ }
45+
46+ // Stamp the declared secrets into the scheduler workflow's engine step, beside
47+ // GITHUB_TOKEN. This is the whole delivery mechanism: GitHub Actions requires each
48+ // secret to be named statically in the workflow, and a task's `required_secrets` is
49+ // exactly that list — so the wiring converge writes it, and a worker then reads
50+ // `process.env.<NAME>` like any other environment variable. No bundle, no parsing,
51+ // no engine-side selection. Regenerated from the stub each converge, so the list
52+ // tracks the declarations rather than accumulating.
53+ export function withDeclaredSecrets ( stubText , names = [ ] ) {
54+ if ( ! names . length ) return stubText ;
55+ const lines = names . map ( ( n ) => ` ${ n } : \${{ secrets.${ n } }}` ) . join ( '\n' ) ;
56+ return stubText . replace ( / ^ ( \s * G I T H U B _ T O K E N : \$ \{ \{ g i t h u b \. t o k e n \} \} ) $ / m, `$1\n${ lines } ` ) ;
57+ }
58+
3859// Re-converge the scheduler workflow to the vendored stub, with the cron minute set
3960// to this repo's stable hashed value (never guessed — hash-minute.mjs, a pure
40- // function of the full name, so re-vendors and this convergence agree). `stubText`
41- // is the vendored stub's content (the caller reads it from the mount). Returns true
42- // when the file was written (absent, or drifted from the target).
43- export function convergeSchedulerWorkflow ( root , fullName , stubText ) {
44- const target = stubText . replace ( / c r o n : \s * ' [ ^ ' ] * ' / , `cron: '${ hashedCron ( fullName ) } '` ) ;
61+ // function of the full name, so re-vendors and this convergence agree) and the
62+ // declared `required_secrets` stamped into the engine step's env. `stubText` is the
63+ // vendored stub's content (the caller reads it from the mount). Returns true when
64+ // the file was written (absent, or drifted from the target).
65+ export function convergeSchedulerWorkflow ( root , fullName , stubText , secretNames = [ ] ) {
66+ const target = withDeclaredSecrets ( stubText , secretNames )
67+ . replace ( / c r o n : \s * ' [ ^ ' ] * ' / , `cron: '${ hashedCron ( fullName ) } '` ) ;
4568 const path = join ( root , SCHEDULER_WORKFLOW ) ;
4669 const current = existsSync ( path ) ? readFileSync ( path , 'utf8' ) : null ;
4770 if ( current === target ) return false ;
@@ -92,9 +115,9 @@ export function removeRetiredCorpusImport(root) {
92115
93116// Converge every wiring surface, returning a flat summary of what changed (empty
94117// when the repo was already converged). `stubText` is the vendored scheduler stub.
95- export function convergeWiring ( root , fullName , stubText ) {
118+ export function convergeWiring ( root , fullName , stubText , secretNames = [ ] ) {
96119 const changed = [ ] ;
97- if ( convergeSchedulerWorkflow ( root , fullName , stubText ) ) changed . push ( SCHEDULER_WORKFLOW ) ;
120+ if ( convergeSchedulerWorkflow ( root , fullName , stubText , secretNames ) ) changed . push ( SCHEDULER_WORKFLOW ) ;
98121 const hooks = ensureHooks ( root ) ;
99122 for ( const h of hooks . added ) changed . push ( `hook:${ h } ` ) ;
100123 if ( removeRetiredCorpusImport ( root ) ) changed . push ( `removed retired ${ CLAUDE_MD } corpus import` ) ;
@@ -111,7 +134,9 @@ async function main() {
111134 const root = process . env . CLAUDINITE_REPO_ROOT || process . cwd ( ) ;
112135 const stubPath = join ( root , '.claudinite/shared/engine/scheduler/stubs/claudinite-scheduler.yml' ) ;
113136 if ( ! existsSync ( stubPath ) ) { console . error ( `converge-wiring: vendored stub not found at ${ stubPath } ` ) ; process . exit ( 1 ) ; }
114- const { changed, error } = convergeWiring ( root , fullName , readFileSync ( stubPath , 'utf8' ) ) ;
137+ const { loadConfig } = await import ( '../checks/helpers/repo-context.mjs' ) ;
138+ const secretNames = await declaredSecrets ( root , loadConfig ( root ) ) ;
139+ const { changed, error } = convergeWiring ( root , fullName , readFileSync ( stubPath , 'utf8' ) , secretNames ) ;
115140 if ( error ) console . log ( `! ${ error } ` ) ;
116141 console . log ( changed . length ? `converge-wiring: ${ changed . join ( ', ' ) } ` : 'converge-wiring: already converged' ) ;
117142}
0 commit comments