@intentius/gitlab-warden 0.1.0

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.
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # gitlab-warden
2
+
3
+ Declarative governance for GitLab **groups & projects** — the whole surface, in one lightweight tool you run in CI.
4
+
5
+ The third [warden](https://github.com/INTENTIUS/github-warden), built on the shared
6
+ provider-agnostic reconcile primitive in
7
+ [`@intentius/chant/reconcile`](https://github.com/INTENTIUS/chant) — the same core
8
+ behind [github-warden](https://github.com/INTENTIUS/github-warden) and
9
+ [forgejo-warden](https://github.com/INTENTIUS/forgejo-warden). gitlab-warden
10
+ supplies the GitLab layer: a REST + GraphQL client (configurable host for
11
+ self-managed + GitLab.com), config + live-state types, a GitLab `diff()`, and the
12
+ reconcile cycles.
13
+
14
+ > 🚧 **Preview published; engine in progress.** `@intentius/gitlab-warden@0.1.0` is a
15
+ > placeholder that establishes the package + release pipeline. The reconcile engine
16
+ > is being built per the [roadmap epic](https://github.com/INTENTIUS/gitlab-warden/issues/21).
17
+
18
+ ## What it is
19
+
20
+ You declare desired state in YAML (selective-by-omission — an absent field is never
21
+ touched); warden diffs it against the live GitLab API and, in `apply` mode,
22
+ converges it — guarded so a typo can't mass-delete.
23
+
24
+ It's a **single binary + a YAML file in CI**: no state file, no HCL, no provider
25
+ toolchain to stand up. That's the whole point — governance-as-code without the
26
+ weight, covering the **full** GitLab governance surface in one place:
27
+
28
+ - **Stateless** — diff against live, reconcile. Nothing to drift, import, or lock.
29
+ - **Continuous drift correction** — a reconcile loop, not a one-shot apply.
30
+ - **Selective-by-omission + ownership-gated deletes** — manage a slice of a large instance without claiming the rest.
31
+ - **Guardrails + dry-run default** — removal cap, lockout protection.
32
+ - **Tier-graceful** — Premium/Ultimate-gated endpoints that 403 are reported and skipped, never fatal.
33
+
34
+ ### Flagship: push-rule drift
35
+
36
+ GitLab push rules aren't version-controlled and their inheritance is **broken** —
37
+ copied at project creation, never propagated; change a group rule and existing
38
+ projects don't get it (each fixed by hand). A reconcile loop that re-asserts
39
+ declared push rules across a whole group tree fixes that, continuously. It's the
40
+ sharpest single example of the model, but warden goes after the *entire* surface.
41
+
42
+ ## Coverage (the full surface)
43
+
44
+ | Scope | Cycles |
45
+ |-------|--------|
46
+ | **Group** | settings · members · subgroup provisioning · variables · webhooks · push rules · access tokens · protected environments · integrations · MR approval settings · compliance frameworks · security policies · member roles |
47
+ | **Project** | settings · members · protected branches · protected tags · protected environments · push rules · MR approvals · variables · webhooks · integrations · deploy keys/tokens · access tokens · advanced protections (job-token scope, registry/package protection) · compliance assignment · security policy attachment |
48
+ | **Instance** (self-managed) | application settings · instance CI/CD variables · system hooks · custom member roles |
49
+
50
+ REST for most of it; **GraphQL** for the few surfaces that require it (compliance
51
+ frameworks, security-policy attachment, custom roles).
52
+
53
+ ## The one genuinely hard part
54
+
55
+ GitLab is a *tree* (nested groups, inherited membership), not a flat org. Membership
56
+ must diff against **direct** members (`/members`) while *reading* effective members
57
+ (`/members/all`), and must **never** treat an inherited member as deletable drift
58
+ (the DELETE fails — the grant lives at an ancestor). This is the credibility gate;
59
+ it gets a dedicated design issue (#3) that the members cycle implements against.
60
+ Everything else is a straightforward cycle on the shared harness.
61
+
62
+ ## How it relates to the sibling wardens
63
+
64
+ | | github-warden | forgejo-warden | gitlab-warden |
65
+ |---|---|---|---|
66
+ | Hierarchy | flat org → repo | flat org → repo | **nested groups → projects** |
67
+ | Membership | direct | team-driven | **direct + inherited** |
68
+ | Auth | GitHub App | token, self-hosted | token, self-managed + SaaS |
69
+ | API | REST | REST | **REST + GraphQL** |
70
+ | Reconcile core | `@intentius/chant/reconcile` | (same) | (same) |
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * gitlab-warden bin launcher.
4
+ *
5
+ * Committed so it exists at npm pack-validation time (before `prepublishOnly`/
6
+ * `build` runs). It loads the built dist/cli.js and calls the exported `run()`.
7
+ */
8
+
9
+ import(new URL("../dist/cli.js", import.meta.url).href)
10
+ .then((mod) => mod.run(process.argv.slice(2)))
11
+ .catch((err) => {
12
+ process.stderr.write(`gitlab-warden: fatal: ${err?.message ?? err}\n`);
13
+ process.exit(3);
14
+ });
package/dist/cli.js ADDED
@@ -0,0 +1,31 @@
1
+ import{createRequire as __cr}from'module';const require=__cr(import.meta.url);
2
+
3
+ // src/cli.ts
4
+ var VERSION = "0.1.0";
5
+ var USAGE = `gitlab-warden ${VERSION} \u2014 declarative governance for GitLab groups & projects
6
+
7
+ \u26A0\uFE0F Early preview. The reconcile engine is under active development.
8
+ Roadmap: https://github.com/INTENTIUS/gitlab-warden/issues/21
9
+
10
+ Planned usage:
11
+ gitlab-warden reconcile --config <path> [--mode dry-run|apply] [--cycles ...]
12
+
13
+ Flags:
14
+ -h, --help Show this help
15
+ -v, --version Print the version
16
+
17
+ Today this command prints help only; functional reconcile arrives with the
18
+ cycles on the roadmap.
19
+ `;
20
+ async function run(argv = []) {
21
+ const arg = argv[0];
22
+ if (arg === "-v" || arg === "--version") {
23
+ process.stdout.write(`${VERSION}
24
+ `);
25
+ return;
26
+ }
27
+ process.stdout.write(USAGE);
28
+ }
29
+ export {
30
+ run
31
+ };
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@intentius/gitlab-warden",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "Declarative governance for GitLab groups & projects — stateless, drift-correcting reconcile in CI (no Terraform state, no Ultimate)",
6
+ "license": "Apache-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/INTENTIUS/gitlab-warden.git"
10
+ },
11
+ "homepage": "https://github.com/INTENTIUS/gitlab-warden#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/INTENTIUS/gitlab-warden/issues"
14
+ },
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "bin": {
19
+ "gitlab-warden": "bin/gitlab-warden.js"
20
+ },
21
+ "files": [
22
+ "bin",
23
+ "dist"
24
+ ],
25
+ "scripts": {
26
+ "tsc": "tsc --noEmit",
27
+ "test": "vitest run",
28
+ "build": "esbuild src/cli.ts --bundle --platform=node --format=esm --banner:js=\"import{createRequire as __cr}from'module';const require=__cr(import.meta.url);\" --outfile=dist/cli.js && chmod +x dist/cli.js",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.0.0",
33
+ "esbuild": "^0.28.0",
34
+ "typescript": "^5.9.3",
35
+ "vitest": "^4.1.9"
36
+ }
37
+ }