Files
spinifex/internal
Josh Siviour f0a704cfcd refactor: move the gateway's public routes, admin wiring and unread config out (11d-1) (#1378)
* test: pin the order of the unauthenticated mounts against SigV4 and the throttle

* refactor: mount the OIDC and registry routes through a public-routes hook

* refactor: take the admin registration name and request-id context as gateway config

* refactor: move node discovery out of the gateway into the awsgw role

* refactor: drop the gateway config fields production never reads (Config, RootCAs, InternalSuffix)

* refactor: drop the gateway Version and Commit config fields

* refactor: compose the production gateway config in one tested function

* refactor: drop the gateway IAMService, STSService, NATSConn and ExpectedNodes config fields

Production never reads them: composeGateway builds Policy and Readiness from
its own inputs. Tests that used the config as a carrier now keep those
collaborators in gwfix.Deps, recorded per config with gwfix.WithDeps and read
back with gwfix.DepsOf. Gateway non-test code now imports nothing under
domains/, runtime/ or operator/.

Test edits are setup only: 153 GatewayConfig literals in 45 files had the four
keys moved into gwfix.WithDeps(..., gwfix.Deps{...}), and 113 field reads
became gwfix.DepsOf(x).{IAM,STS,NATS,Nodes}. No assertion changed.

Script 1, literal rewrite (balanced-brace scanner, keys moved out of the
literal):

    import re,sys,subprocess,os
    KEYS={'IAMService':'IAM','STSService':'STS','NATSConn':'NATS','ExpectedNodes':'Nodes'}
    def scan_close(s,i):
        # s[i]=='{' ; return index of matching '}' ; skips strings, runes, comments
        d=0;n=len(s);k=i
        while k<n:
            c=s[k]
            if c=='"':
                k+=1
                while s[k]!='"':
                    k+= 2 if s[k]=='\\' else 1
            elif c=='`':
                k=s.index('`',k+1)
            elif c=="'":
                k+=1
                while s[k]!="'":
                    k+= 2 if s[k]=='\\' else 1
            elif s.startswith('//',k):
                k=s.index('\n',k)
                continue
            elif s.startswith('/*',k):
                k=s.index('*/',k)+1
            elif c in '{([': d+=1
            elif c in '})]':
                d-=1
                if d==0: return k
            k+=1
        raise Exception('unbalanced')
    def split_top(body):
        parts=[];d=0;start=0;k=0;n=len(body)
        while k<n:
            c=body[k]
            if c=='"':
                k+=1
                while body[k]!='"': k+= 2 if body[k]=='\\' else 1
            elif c=='`': k=body.index('`',k+1)
            elif c=="'":
                k+=1
                while body[k]!="'": k+= 2 if body[k]=='\\' else 1
            elif body.startswith('//',k):
                k=body.index('\n',k); continue
            elif body.startswith('/*',k): k=body.index('*/',k)+1
            elif c in '{([': d+=1
            elif c in '})]': d-=1
            elif c==',' and d==0:
                parts.append(body[start:k]); start=k+1
            k+=1
        parts.append(body[start:])
        return parts
    changed={}
    files=[l for l in subprocess.check_output(['git','ls-files','*.go']).decode().split() if os.path.exists(l)]
    SKIP={'spinifex/gateway/gateway.go','spinifex/runtime/roles/awsgw/compose.go','spinifex/runtime/roles/awsgw/awsgw.go'}
    lit=re.compile(r'(&?)((?:gateway\.)?GatewayConfig)\{')
    total=0
    for f in files:
        if f in SKIP: continue
        s=open(f).read()
        if 'GatewayConfig{' not in s: continue
        out=[];pos=0;cnt=0
        for m in lit.finditer(s):
            if m.start()<pos: continue
            o=m.end()-1
            c=scan_close(s,o)
            body=s[o+1:c]
            parts=split_top(body)
            keep=[];moved=[]
            for p in parts:
                km=re.match(r'\s*(IAMService|STSService|NATSConn|ExpectedNodes)\s*:\s*(.*)$',p,re.S)
                if km: moved.append((KEYS[km.group(1)],km.group(2).strip()))
                else: keep.append(p)
            if not moved: continue
            # rebuild
            kb=','.join(keep)
            if kb.strip()=='' : kb=''
            elif not kb.rstrip().endswith('\n') and '\n' in body: pass
            d=', '.join(f'{k}: {v}' for k,v in moved)
            new=f'gwfix.WithDeps({m.group(1)}{m.group(2)}{{{kb}}}, gwfix.Deps{{{d}}})'
            if not m.group(1): new=f'gwfix.WithDeps(&{m.group(2)}{{{kb}}}, gwfix.Deps{{{d}}})'  # value literals flagged below
            out.append((m.start(),c+1,new)); pos=c+1; cnt+=1
            if not m.group(1): print('VALUE LITERAL',f,s.count('\n',0,m.start())+1)
        if not out: continue
        r=[];last=0
        for a,b,n in out:
            r.append(s[last:a]);r.append(n);last=b
        r.append(s[last:])
        s2=''.join(r)
        if 'testkit/gwfix"' not in s2:
            s2=re.sub(r'import \(\n','import (\n\t"github.com/mulgadc/spinifex/internal/testkit/gwfix"\n',s2,1)
        open(f,'w').write(s2); total+=cnt; changed[f]=cnt
    print('literals rewritten',total,'files',len(changed))

Script 2, read rewrite (driven by the compiler's "has no field or method"
errors for the type, so only GatewayConfig receivers are touched):

    import re,subprocess,sys,collections
    M={'IAMService':'IAM','STSService':'STS','NATSConn':'NATS','ExpectedNodes':'Nodes'}
    def run():
        env=dict(__import__('os').environ,GOWORK='off')
        out=''
        for tags,pk in [('',['./spinifex/gateway','./spinifex/runtime/roles/awsgw/...']),('integration',['./tests/integration/...'])]:
            cmd=['go','test','-count=1','-run','NONE_X','-gcflags=-e']+(['-tags',tags] if tags else [])+pk
            r=subprocess.run(cmd,capture_output=True,text=True,env=env)
            out+=r.stdout+r.stderr
        return out
    fixed=0
    for it in range(8):
        out=run()
        errs=re.findall(r'^(\S+\.go):(\d+):(\d+): (.*?) undefined \(type (\*?(?:gateway\.)?GatewayConfig) has no field or method (\w+)\)',out,re.M)
        if not errs:
            print(out[-3000:]); break
        byfile=collections.defaultdict(list)
        for f,l,c,expr,t,fld in errs: byfile[f].append((int(l),int(c),expr,fld))
        for f,es in byfile.items():
            lines=open(f).read().split('\n')
            for l,c,expr,fld in sorted(set(es),reverse=True):
                line=lines[l-1]
                # expr like 'gw.IAMService' or 'x.y.IAMService'
                new=f'gwfix.DepsOf({expr[:-len(fld)-1]}).{M[fld]}'
                if expr not in line: print('MISS',f,l,expr); continue
                lines[l-1]=line.replace(expr,new)
                fixed+=1
            s='\n'.join(lines)
            if 'testkit/gwfix"' not in s:
                s=re.sub(r'import \(\n','import (\n\t"github.com/mulgadc/spinifex/internal/testkit/gwfix"\n',s,count=1)
            open(f,'w').write(s)
    print('read sites fixed',fixed)

* docs: record the gateway mount hooks and config-field removal in the package boundary migration

* docs: fill in the 11d-1 PR number in the package boundary migration
2026-11-01 13:04:31 +10:00
..
2026-09-30 11:48:27 +10:00
2026-08-20 10:52:03 +10:00