feat: implement T01-T04 — Go module, canonical model, LDAP validator, error taxonomy

- T01: Go module (keycape), full directory skeleton, Makefile, CI workflow
- T02: spec/canonical-model.yaml with 6 entities + Go domain types
- T03: spec/ldap-schema.yaml + validator binary with structural/semantic rules
- T04: Error taxonomy — 4 stable error types, JSON format, HTTP helpers

28 tests pass, go vet clean, go build clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 01:27:54 +01:00
parent f3b1cdcba4
commit 329e996619
21 changed files with 1992 additions and 0 deletions

View File

@@ -0,0 +1,13 @@
// keycape-to-keycloak migrates a KeyCape canonical snapshot to a Keycloak
// realm export format. Part of the NetKingdom IAM migration contract.
package main
import (
"fmt"
"os"
)
func main() {
fmt.Fprintln(os.Stderr, "keycape-to-keycloak: not yet implemented (T06+)")
os.Exit(1)
}

14
src/cmd/keycape/main.go Normal file
View File

@@ -0,0 +1,14 @@
// keycape is the main server binary for the KeyCape IAM profile service.
// It orchestrates Authelia, LLDAP, and privacyIDEA to implement the
// NetKingdom IAM Profile (OIDC/PKCE Authorization Code Flow).
package main
import (
"fmt"
"os"
)
func main() {
fmt.Fprintln(os.Stderr, "keycape server: not yet implemented (T05+)")
os.Exit(1)
}

View File

@@ -0,0 +1,13 @@
// lldap-export exports the LLDAP directory as a canonical YAML snapshot
// for use with the validator and migration tools.
package main
import (
"fmt"
"os"
)
func main() {
fmt.Fprintln(os.Stderr, "lldap-export: not yet implemented (T06+)")
os.Exit(1)
}

View File

@@ -0,0 +1,13 @@
// lldap-to-ldap migrates LLDAP directory data to standard LDAP (LDIF format).
// Part of the NetKingdom IAM migration contract.
package main
import (
"fmt"
"os"
)
func main() {
fmt.Fprintln(os.Stderr, "lldap-to-ldap: not yet implemented (T06+)")
os.Exit(1)
}

69
src/cmd/validator/main.go Normal file
View File

@@ -0,0 +1,69 @@
// validator is the CLI binary for the KeyCape canonical LDAP schema validator.
// It reads a YAML directory snapshot and emits a machine-readable JSON report.
//
// Usage:
//
// validator --mode=ci|provisioning|migration --input=<snapshot.yaml>
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"gopkg.in/yaml.v3"
"keycape/internal/domain"
"keycape/internal/validator"
)
func main() {
mode := flag.String("mode", "ci", "validation mode: ci, provisioning, or migration")
input := flag.String("input", "", "path to YAML directory snapshot (required)")
flag.Parse()
if *input == "" {
fmt.Fprintln(os.Stderr, "error: --input is required")
flag.Usage()
os.Exit(2)
}
m := validator.Mode(*mode)
switch m {
case validator.ModeCI, validator.ModeProvisioning, validator.ModeMigration:
// valid
default:
fmt.Fprintf(os.Stderr, "error: unknown mode %q (must be ci, provisioning, or migration)\n", *mode)
os.Exit(2)
}
data, err := os.ReadFile(*input)
if err != nil {
fmt.Fprintf(os.Stderr, "error reading input: %v\n", err)
os.Exit(1)
}
var dir domain.Directory
if err := yaml.Unmarshal(data, &dir); err != nil {
fmt.Fprintf(os.Stderr, "error parsing YAML: %v\n", err)
os.Exit(1)
}
snap := validator.Snapshot{
Users: dir.Users,
Groups: dir.Groups,
}
report := validator.Validate(snap, m)
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
if err := enc.Encode(report); err != nil {
fmt.Fprintf(os.Stderr, "error encoding report: %v\n", err)
os.Exit(1)
}
if !report.Passed {
os.Exit(1)
}
}