Skip to content

Phase 6b: gitlab runner port - #60

Closed
grubmeshi wants to merge 1 commit into
refactor/single-go-binary/phase-6a-manualfrom
refactor/single-go-binary/phase-6b-gitlab
Closed

Phase 6b: gitlab runner port#60
grubmeshi wants to merge 1 commit into
refactor/single-go-binary/phase-6a-manualfrom
refactor/single-go-binary/phase-6b-gitlab

Conversation

@grubmeshi

Copy link
Copy Markdown
Collaborator

Phase 6b — gitlab runner port

Second of the four stacked Kotlin→Go port PRs. Base: phase-6a-manual.

Ports the GitLab CI/CD-trigger runner to Go (internal/gitlab, cmd/gitlab, cmd/bbrunner gitlab, container). Async handover per D9: register → one final IN_PROGRESS+SUCCEEDED or FAILED+FAILED update. Ships the umbrella-assigned shared artifacts first:

  • internal/meshapi/decryptor.go — shared Decryptor/CertDecryptor/NoopDecryptor (the CertDecryptor empty-string guard reproduces Kotlin decrypt("") == "", T8/G-P11).
  • internal/meshapi/decryptinputs.go — byte-based DecryptInputs (raw run JSON in/out, byte-preserving forwarding for MESHSTACK_RUN, §16.6) preserving the impl-secret-vs-input decryption asymmetry structurally.
  • containers/runner-config.yml — the shared base config layer carrying the well-known dev private key.

internal/gitlab new at 92.1% (gate 90). Secret-hygiene leak test proves MESHSTACK_RUN never carries the decrypted trigger token while sensitive STRING/CODE/FILE inputs are decrypted. Build, race tests, lint (0 issues), coverage gate all pass on the stacked tree.

Sanctioned deltas / deferrals

  • Kotlin dev-key PEM bug fixed: the Kotlin classpath yaml bakes the shared dev key as one unbroken base64 line, which Go's stdlib encoding/pem refuses; re-wrapped the identical DER bytes into standard multi-line PEM (internal/config/basekey_test.go proves byte-parity). Load-bearing for 06C/06D.
  • Kotlin module removal / CI flip / meshfed touch-points deferred to the acceptance-gated flip PR (CROSS_REPO_TODO.md); plan §15 confirms gitlab needs no mandatory cross-repo edits.
  • Kotlin pin tests (G-P1–G-P13) not added (gradle not runnable); Go scenario suite is the surviving pin.

🤖 Generated with Claude Code

Ports PLAN_DETAIL_06B_gitlab.md, reusing the 06A manual template unchanged (handler
shape, event-driven report.Reporter seam, config compat, persona wiring, Dockerfile
pattern). Additive and always-green: the Kotlin gitlab-block-runner module and its
Gradle/CI legs are untouched (removal + the acceptance gate + the meshfed-release
touch-points are deferred to the flip PR, see CROSS_REPO_TODO.md — they need a live
GitLab/meshStack this environment can't provide).

Umbrella-assigned shared artifacts shipped here (first consumer, per 06A §4.4/§17):
- meshapi.Decryptor + CertDecryptor/NoopDecryptor: the shared decrypt seam every
  phase-6 port injects, with the Kotlin decrypt("") == "" empty-string guard (T8/G-P11)
  the underlying crypto lacked.
- meshapi.DecryptInputs: decrypts ONLY sensitive STRING/CODE/FILE input values
  (Kotlin's decryptBlockRunInputs asymmetry, umbrella §7.6) — the structural fix so an
  outbound payload can never carry a decrypted implementation secret. Table-driven
  tests incl. a real-keypair cross-check and the secret-hygiene leak assertion.
- gitlab.ExternalCallError: the MeshHttpException equivalent (06A §4.4 shape), with the
  four-way GitLab error classification (404 / identity-verification / generic /
  undeserializable body) collapsing into the one wire-visible FAILED update Kotlin sends.

Gitlab persona (internal/gitlab, cmd/gitlab, bbrunner gitlab subcommand):
- One external POST (multipart trigger payload: token/ref/MESHSTACK_BEHAVIOR/
  MESHSTACK_RUN + env/non-env inputs + the four callback URLs, missing-link ⇒ omit),
  redirects disabled at the client (G-P10), base-URL sanitization ported from
  UrlSanitizerService.
- Always-async handover (D9): register one PENDING gl-trigger step, then exactly one
  final update — IN_PROGRESS+SUCCEEDED on a successful trigger, FAILED+FAILED on any
  trigger-path failure — never a terminal SUCCEEDED from the runner itself.
- Polling (dispatch.Loop + InProcess, mgmt on 8103, PORT alias) and single-run (file
  source, NoOp decryptor — the k8s controller pre-decrypted both inputs and the trigger
  token, §2.6 caveat pinned as-is — R12 exit rule).
- New shared top-level base containers/runner-config.yml (deep-merged under the
  per-impl file, base < per-impl < env): carries the well-known dev private key shared
  by gitlab/azdevops/github, re-wrapped into standard multi-line PEM (Go's stdlib
  encoding/pem rejects the Kotlin classpath's unbroken single-line form; DER bytes are
  byte-identical, verified in internal/config/basekey_test.go).

Coverage gate: internal/gitlab joins thresholds.txt at 90 (measures 92.1%, no
exclusions) via the scenario suite (meshapitest + a fake GitLab httptest server) plus
keep-as-unit tables for the pure mappings (valueString, sanitizeBaseUrl, error
classification). Manually verified end-to-end via `docker build` + `docker run` against
a hand-rolled mock meshfed API: claim → register → trigger attempt → FAILED report all
observed with the exact pinned message shapes.

Kotlin pin tests (G-P1–G-P13) were not added — gradle is not runnable in this
environment and the module isn't being modified/removed in this commit; recorded as a
flagged follow-up in CROSS_REPO_TODO.md rather than silently skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@grubmeshi

Copy link
Copy Markdown
Collaborator Author

Superseded by #65, which consolidates the plan + all phases 0→7 + the phase-3/5 remediation into a single reviewable PR (with all plan/run artifacts and a code-verified FOLLOW_UP.md). Closing in favor of #65.

@grubmeshi grubmeshi closed this Jul 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant