Commit ae7254f
* feat(secret): add git credential type
Adds the 'git' credential type with token, ssh_key, and username fields,
plus a RequireAtLeastOne validator ensuring token or ssh_key is always present.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(audit): add repo.{added,updated,removed} actions
* feat(db): add 019_git_repos migration
* feat(repo): add Repo struct and validators
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(repo): add Store.Create with audit emission
Implements the first CRUD method on the repo.Store type. Create validates
name and poll_interval before touching the database, inserts the row, emits
a repo.added audit event inside the same transaction, and returns the
persisted Repo struct. Four integration tests cover the happy path, duplicate
name rejection, and both validation guards.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(repo): add Store.Get with nullable last-sync scan
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(repo): add Store.List
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(repo): add Store.Update for mutable fields
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(repo): add Store.Delete with audit emission
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(config): add git_sync.repos block
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(cli): add mantle repos root command
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(cli): add mantle repos add
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(cli): add mantle repos list
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(cli): add mantle repos status
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(cli): add mantle repos remove
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* docs: document mantle repos CLI and git_sync config
* fix: address pre-push reviewer feedback for git sync foundation
- Clarify that Plan A ships registry + CLI only (sync engine lands later)
- Rewrite --prune flag help to state the positive behavior
- Mark webhook_secret as sensitive in Repo struct
- Reject repo URLs that embed credentials inline
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(repo): reject any userinfo in URL, not just passwords
GitHub PATs are valid as a username in HTTPS URLs (user@host), so the
previous check (hasPassword only) left a bypass. Now any u.User != nil
is rejected. Renamed TestStore_Create_AllowsURLWithUsernameOnly to
_RejectsURLWithUsernameOnly and flipped the assertion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(repo): include last_sync_* and webhook_secret in Update RETURNING clause
The Update method was returning a partial Repo that silently dropped
last_sync_sha/at/error and webhook_secret. The RETURNING clause now
mirrors Get's SELECT list, and the subsequent scan populates the
nullable fields using sql.NullString/NullTime with the same pattern as Get.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(repo): preserve existing Branch/Path in Update when caller passes empty
Empty Branch/Path in UpdateParams previously overwrote the stored
value with an empty string. Now a CASE expression in the SQL
keeps the stored value when the caller passes ''. This is equivalent
to Option B (preserve-on-empty) without a second round-trip.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(migration): add ON DELETE RESTRICT to git_repos.team_id FK
Without an explicit action the FK defaulted to NO ACTION (functionally
RESTRICT but implicit). Adding RESTRICT makes the intent unambiguous:
deleting a team with registered repos is a hard error, not silent
cascade. Operators must remove repos before removing the team.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(config): validate git_sync.repos entries in Load
Load now iterates cfg.GitSync.Repos and validates each entry:
- repo.ValidateName for the name field
- url is required and must not embed userinfo (same rule as store.Create)
- repo.ValidatePollInterval when poll_interval is non-empty
No import cycle: internal/repo production code does not import
internal/config (only repo's _test.go does, which is excluded from
the dependency graph).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(migration): rework git_repos enabled index to composite (team_id, enabled)
Rename idx_git_repos_enabled to idx_git_repos_team_enabled and make it a
composite (team_id, enabled) partial index. Queries that filter by team
and enabled status now hit a single index instead of intersecting two.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(cli): add Long+Example to repos status/list; move repos to admin group; comment GitSync env binding
- repos status and repos list were missing Long descriptions and Example
blocks; add both, styled to match repos add.
- newReposCommand belongs with admin/gitops infra config, not the workflow
lifecycle group; move it to the admin group in root.go.
- Add inline comment on GitSyncConfig.Repos explaining why no BindEnv
calls are present (Viper cannot bind list-valued env vars).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test: cover Path/PollInterval/Prune in GitSync config test; add empty-string git cred case
- TestLoadConfig_GitSyncRepos was only asserting Name, URL, Branch,
Credential, AutoApply. Add explicit checks for Path, PollInterval,
and Prune so the full parsed struct is verified.
- TestGitCredentialType_ValidateRequiresTokenOrSSHKey gains a case
for {"token": "", "ssh_key": ""} — present-but-empty values must
also fail the RequireAtLeastOne check.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(test): tighten empty-state assertion; extract seedRepo helper
- TestReposList_EmptyState: replace strings.Contains with an exact
strings.TrimSpace equality check so stray whitespace or extra output
would surface as a failure rather than passing silently.
- Extract the repeated four-line "seed a repo via add" block into a
seedRepo(t, cfg, name) helper; replace all four inline copies.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(repo): tighten validRepoNamePattern to reject trailing hyphens and underscores
Change from ^[a-z0-9][a-z0-9_-]*$ to ^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$
so that names like "my-" or "repo_" are rejected. The updated pattern
requires the last character to be alphanumeric while still permitting
single-character names (pure alnum).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(config): inline git_sync validators to break test import cycle
The config → repo → auth chain caused `go test` to fail in both
./internal/auth and ./internal/repo with "import cycle not allowed in
test". Fix by inlining validateGitSyncName, validateGitSyncPollInterval,
and validateGitSyncURL as unexported helpers in config.go, removing the
import edge from config → repo. The canonical exported symbols in
internal/repo/types.go are unchanged.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(git-sync): address PR #133 review findings
Inline (required):
- config.go: validate that each git_sync.repos entry has a non-empty
credential; the DB has credential NOT NULL, so a missing value would
fail at persistence time with a cryptic constraint error
- 019_git_repos.sql: drop webhook_secret column — storing an HMAC secret
as plaintext TEXT is a security risk; Plan C will add a proper migration
that stores it as an encrypted credential reference
- store.go, types.go: remove webhook_secret from RETURNING/SELECT queries,
scans, and the Repo struct to match the updated schema
Nitpicks:
- audit.go: align repo action verbs with the rest of the domain:
repo.added → repo.created, repo.removed → repo.deleted (constant
identifiers unchanged to minimise callsite churn)
- repos_test.go: remove unused context return from reposCtx; callers
already discarded it with _ and pass the DB URL via --database-url flags
- config_test.go: add TestLoad_GitSyncRepos_Validation with table-driven
negative cases covering invalid name, missing url, embedded credentials,
url with no scheme/host, missing credential, and poll_interval below 10s
- config.go: tighten validateGitSyncURL to reject strings that url.Parse
accepts as relative references but lack both scheme and host, unless they
match the scp-style SSH format (git@host:path)
- 019_git_repos.sql: add comment explaining why credential is a plain name
reference rather than a FK — prevents registration before the credential
exists, which is a common IaC bootstrapping pattern
https://claude.ai/code/session_01MqwQDFpdQTksPDHrMi4cKB
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent ac6a6dd commit ae7254f
13 files changed
Lines changed: 1474 additions & 3 deletions
File tree
- packages/engine/internal
- audit
- cli
- config
- db/migrations
- repo
- secret
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
83 | 87 | | |
84 | 88 | | |
85 | 89 | | |
| |||
91 | 95 | | |
92 | 96 | | |
93 | 97 | | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
94 | 119 | | |
95 | 120 | | |
96 | 121 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
64 | 69 | | |
65 | 70 | | |
66 | 71 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
0 commit comments