diff --git a/.github/workflows/kubernetes-reference-quality.yml b/.github/workflows/kubernetes-reference-quality.yml new file mode 100644 index 000000000..d01a5e664 --- /dev/null +++ b/.github/workflows/kubernetes-reference-quality.yml @@ -0,0 +1,46 @@ +name: Kubernetes Reference Quality + +on: + pull_request: + branches: + - develop + push: + branches: + - develop + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: kubernetes-reference-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + kubernetes-reference: + name: Hardened reference deployment contract + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout exact candidate + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Prove exact candidate checkout + env: + EXPECTED_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + run: test "$(git rev-parse HEAD)" = "${EXPECTED_SHA}" + - name: Set up Node.js LTS + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0 + with: + node-version: "24" + check-latest: false + - name: Exercise hardened Kubernetes reference contract + run: node --test tests/kubernetes-reference.test.mjs + - name: Validate repository contracts + run: npm run validate + - name: Prove clean checkout + run: | + git diff --exit-code + test -z "$(git status --porcelain)" diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 1bdc13b5a..21454081d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -105,3 +105,7 @@ The foundation starts as a monorepo with separately deployable services. Each se ## Security posture Access is tenant-, actor-, purpose-, resource-, and lifetime-scoped. High-impact decisions require preview, explicit human confirmation, versioned evidence, immutable decision records, and attributable audit events. LLM outputs are draft evidence only. + +## Kubernetes reference deployment boundary + +The active deployment lane carries the provider-neutral People API reference at `infrastructure/kubernetes/people-api-reference.json`. It is an architecture example for extracting the People API behind the same published API/data-ownership boundary; it does not authorize a release and does not make Kubernetes part of protected-branch runtime truth until this lane is integrated. The manifest keeps the image non-runnable until an immutable digest from an integrated protected revision is supplied, preserves Restricted Pod Security Admission and default-deny network isolation, and requires environment-specific node-CIDR, DNS, PostgreSQL, admission and CNI validation. No Kubernetes object may introduce direct cross-service application-table access or weaken Orgmetra's service-owned schema boundary. diff --git a/docs/doctoring/kubernetes-reference-deployment-references.md b/docs/doctoring/kubernetes-reference-deployment-references.md new file mode 100644 index 000000000..0db388661 --- /dev/null +++ b/docs/doctoring/kubernetes-reference-deployment-references.md @@ -0,0 +1,26 @@ +# Kubernetes reference deployment — primary-source doctoring + +**Evidence state:** active-PR design evidence. These sources inform the Orgmetra-owned reference deployment; they do not constitute Kubernetes certification, cloud-provider compatibility, SOC 2 evidence by themselves, or release authorization. + +## APA 7 references + +Kubernetes Authors. (n.d.). *Configure liveness, readiness and startup probes*. Kubernetes. Retrieved August 22, 2026, from https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ + +Kubernetes Authors. (n.d.). *Network policies*. Kubernetes. Retrieved August 22, 2026, from https://kubernetes.io/docs/concepts/services-networking/network-policies/ + +Kubernetes Authors. (n.d.). *Pod Security Admission*. Kubernetes. Retrieved August 31, 2026, from https://kubernetes.io/docs/concepts/security/pod-security-admission/ + +Kubernetes Authors. (n.d.). *Pod security standards*. Kubernetes. Retrieved August 22, 2026, from https://kubernetes.io/docs/concepts/security/pod-security-standards/ + +Kubernetes Authors. (n.d.). *Specifying a disruption budget for your application*. Kubernetes. Retrieved August 22, 2026, from https://kubernetes.io/docs/tasks/run-application/configure-pdb/ + +## Design consequences recorded from the primary documentation + +- **Probe roles stay separate.** Startup probes delay takeover by liveness/readiness while a process starts; liveness is for restart decisions; readiness controls whether a pod receives service traffic. Orgmetra therefore keeps `/health` dependency-free and uses `/ready` for owned PostgreSQL readiness rather than making database reachability a liveness condition. +- **Restricted pod intent is explicit and versioned.** The reference declares Restricted Pod Security Admission labels and a pod/container security context that avoids host namespaces, privileged mode and privilege escalation, runs non-root, uses `RuntimeDefault` seccomp and drops Linux capabilities. Kubernetes defines a per-mode `*-version` label; if version is not pinned, the admission controller uses `latest`. Orgmetra therefore pins `enforce`, `audit`, and `warn` to `v1.37` so the authored policy contract does not silently change when the control plane advances. A target on a different minor requires an explicit re-baseline and re-pin, not deletion of the version labels. +- **Network isolation starts deny-by-default.** A NetworkPolicy selecting all pods with both `Ingress` and `Egress` policy types establishes the namespace baseline. Required application and DNS flows are then explicit exceptions. Cluster networking must actually enforce NetworkPolicy before this is treated as isolation evidence. +- **PDB evidence is bounded.** `maxUnavailable: 1` applies to a controller-managed replicated Deployment and limits voluntary evictions; it is not evidence against involuntary node, process or dependency failures. + +## Out of scope for this evidence set + +This doctoring file does not select a managed Kubernetes vendor, CNI, ingress controller, service mesh, cloud load balancer, registry, PostgreSQL provider or image signer. Those choices must be bound to their own current primary documentation and target-environment acceptance evidence before release. diff --git a/docs/traceability/kubernetes-reference-deployment.md b/docs/traceability/kubernetes-reference-deployment.md new file mode 100644 index 000000000..3fb6507c4 --- /dev/null +++ b/docs/traceability/kubernetes-reference-deployment.md @@ -0,0 +1,37 @@ +# Kubernetes reference deployment traceability + +## State legend + +- **Protected-main truth:** accepted on `develop@9e3e4847510e1e612b48474ba42b177b8ed824df`. +- **Active PR:** implemented only on `feat/kubernetes-reference-deployment` until merged. +- **Dependency-active PR:** same-repository capability required for a runnable release but not copied into this branch. +- **Planned:** deliberately outside this bounded slice. + +## Requirement → evidence map + +| Requirement | State | Executable / review evidence | +| --- | --- | --- | +| Provider-neutral Kubernetes reference is absent on protected main and cannot be mistaken for shipped deployment truth | Protected-main truth | Foundation implementation plan Task 10 plus initial RED exact head `18778945f2bdb9d6f42cebd7a3c71c18dad36352` | +| Reference uses immutable image identity rather than a mutable tag | Active PR | `infrastructure/kubernetes/people-api-reference.json`; sentinel requires digest resolution before apply; `tests/kubernetes-reference.test.mjs` | +| Pod uses Restricted-intent security context and no automatic API token | Active PR | Namespace labels, ServiceAccount, Deployment security contexts and adversarial manifest assertions | +| Pod Security Admission behavior is deterministic rather than implicitly drifting with `latest` | Active PR | `enforce-version`, `audit-version`, and `warn-version` are pinned to `v1.37`; `tests/kubernetes-reference.test.mjs`; exact RED head `6b4fc614b57ec09172b6d029a9ea07d2203ad42e`, run `33344167879`, job `99345037244` | +| Startup/liveness and readiness are not conflated | Active PR + dependency-active PR | Reference sends startup/liveness to `/health` and readiness to `/ready`; People API probe implementation is owned by PR #74 and must be integrated into the selected release image before this manifest can be runnable | +| Namespace traffic is deny-by-default | Active PR | `orgmetra-default-deny` plus exact People API ingress/PostgreSQL/DNS exceptions; network-policy assertions | +| Managed PostgreSQL adaptation cannot silently broaden egress | Active PR | `infrastructure/kubernetes/README.md` requires provider-approved exact private-network adaptation while preserving default deny | +| Voluntary disruption and rollout are bounded | Active PR | two replicas, `maxUnavailable: 0` rolling update, PDB `maxUnavailable: 1`, topology-spread preference | +| Target cluster validates object schemas/admission before deployment | Active PR | documented `kubectl apply --dry-run=server ...` precondition; actual target-cluster dry-run remains environment-specific release evidence | +| Release candidate has reproducible source/SBOM/provenance evidence | Dependency-active PR | PR #78; this branch does not duplicate its builder or evidence | +| Image/container build, signed attestation and verified deployable digest exist | Planned | must be produced from one accepted integrated protected head; sentinel intentionally keeps this reference non-runnable until then | +| Production metrics, ingress/TLS, secrets delivery, autoscaling and environment-specific SLOs are accepted | Planned | separate deployment/operability slices; not claimed here | + +## RED → repair evidence + +The initial exact RED head `18778945f2bdb9d6f42cebd7a3c71c18dad36352` materialized Kubernetes Reference Quality run `32564001046`, job `97009809481`. The job proved exact checkout of that SHA and failed in the first contract step because `infrastructure/kubernetes/people-api-reference.json` and `infrastructure/kubernetes/README.md` did not exist. Six deployment-contract tests failed with `ENOENT`; repository validation and clean-checkout proof were therefore correctly skipped rather than treated as passing evidence. + +A second acquisition-grade regression at exact head `6b4fc614b57ec09172b6d029a9ea07d2203ad42e` materialized run `33344167879`, job `99345037244`, proved exact checkout, and produced eight passing tests plus one intentional failure because `pod-security.kubernetes.io/enforce-version` was absent (`undefined` versus expected `v1.37`). The repair pins all three Pod Security Admission modes (`enforce`, `audit`, `warn`) to the authored Kubernetes minor and documents deliberate re-baselining for a target cluster on a different minor. + +The repair adds only Orgmetra-owned reference-deployment artifacts and supporting evidence. It does not mutate a dedicated-writer CWL dependency, add cross-service SQL, publish an image, create cluster resources, or claim that a target cluster has accepted the objects. + +## Release boundary + +A release operator must replace the image sentinel only with a verified digest from the same exact integrated protected revision that contains the required People API probe implementation and passes all applicable build, security, provenance, review, migration, rollback/recovery and target-environment checks together. The checked-in reference itself is therefore buyer-readable deployment intent, not release authorization. diff --git a/infrastructure/kubernetes/README.md b/infrastructure/kubernetes/README.md new file mode 100644 index 000000000..f5f45c35a --- /dev/null +++ b/infrastructure/kubernetes/README.md @@ -0,0 +1,58 @@ +# Orgmetra Kubernetes reference deployment + +This directory contains a provider-neutral **reference**, not a release artifact. Applying it does not authorize a release, does not certify a cluster, and does not replace environment-specific threat modelling or change approval. + +## Release precondition: immutable image identity + +`people-api-reference.json` deliberately contains a non-runnable image sentinel. Before any server-side apply or rollout, replace it with an Orgmetra People API image qualified by a **verified 64-character lowercase SHA-256 image digest**: + +```text +ghcr.io/contextualwisdomlab/orgmetra-people-api@sha256:<64-lowercase-hex> +``` + +The digest must be resolved from the same integrated protected source revision that passed the applicable build, security, SBOM, provenance, migration, recovery, review and release-authorization gates. A mutable tag is not an acceptable substitute. The current repository reference does not publish such an image and therefore must remain non-runnable until that evidence exists. + +The probe paths also assume the selected image contains the governed People API `/health` and `/ready` contracts. Do not deploy a protected revision that predates those endpoints merely to satisfy the manifest shape. + +## Pod hardening + +The `orgmetra-system` namespace declares `pod-security.kubernetes.io/enforce=restricted` and matching `audit`/`warn` Restricted Pod Security Admission labels. It pins the policy contract with `pod-security.kubernetes.io/enforce-version=v1.37`, `pod-security.kubernetes.io/audit-version=v1.37`, and `pod-security.kubernetes.io/warn-version=v1.37`. The explicit version labels are deliberate: an omitted version uses the admission controller's `latest` policy, which can change the effective Restricted contract after a Kubernetes upgrade. The People API pod is non-root, uses `RuntimeDefault` seccomp, disables service-account token automount, privilege escalation, privileged mode and host namespaces, drops every Linux capability, and uses a read-only root filesystem. Because the root filesystem is read-only, the pod declares a dedicated `tmp-scratch` `emptyDir` mounted at `/tmp`; do not remove it and do not replace it with a `hostPath`. + +Cluster operators must verify that admission controls actually enforce the intended Restricted profile. A target cluster whose supported policy minor differs from `v1.37` must not make the reference deployable by deleting or weakening the version labels. Re-baseline the policy deliberately against that cluster's authoritative Kubernetes documentation, update the pinned minor and regression evidence together, and prove the resulting objects with server-side dry-run before release. If the environment injects sidecars or init containers, those injected containers must independently satisfy the same effective policy. + +## Liveness and readiness + +- startup and liveness use `GET /health`, which is process-liveness only; +- readiness uses `GET /ready`, which checks the People API's owned PostgreSQL dependency; +- dependency failure removes a pod from service traffic rather than making liveness depend on PostgreSQL. + +The timing values are bounded reference defaults, not universal tuning values. Validate them against measured startup and dependency-recovery behaviour before production use. + +## Network isolation + +The namespace starts from default-deny ingress and egress. The People API policy then permits only: + +1. TCP/8080 ingress from same-namespace pods explicitly labelled `orgmetra.cwl/people-api-client=true`; +2. TCP/8080 ingress from the kubelet probe source range, modelled by the RFC 5737 TEST-NET-1 placeholder `192.0.2.0/24`. **Replace that `ipBlock.cidr` with the exact node CIDR your cluster's kubelet probes originate from** (or the documented per-node ranges); without it, a default-deny CNI drops HTTP health probes and every pod fails liveness; +3. TCP/5432 egress to same-namespace pods labelled `app.kubernetes.io/name=orgmetra-postgres`; +4. DNS to kube-system pods labelled `k8s-app=kube-dns` over UDP/TCP 53. + +A **NetworkPolicy-capable CNI** is required. If the selected cluster networking implementation does not enforce Kubernetes NetworkPolicy, do not claim that this reference provides network isolation. + +The checked-in PostgreSQL rule models an in-cluster owned database. For **managed PostgreSQL**, replace the database egress rule with the provider-approved private-network policy for the exact database endpoints while preserving default-deny semantics. Do not broaden egress to `0.0.0.0/0` as a convenience workaround. Likewise, adapt the DNS selector only to the cluster's authoritative DNS implementation and keep that exception narrowly scoped. + +Keyverse, Naruon and other dedicated-writer CWL services are intentionally absent from this egress policy. Add a foreign-service network path only when a published adapter/API contract and environment-specific authorization design require it; never add cross-service application-table access. + +## Availability boundary + +The reference uses two replicas, a rolling update with `maxUnavailable: 0`, and a PodDisruptionBudget with `maxUnavailable: 1`. The PDB constrains voluntary disruption only; it does not protect against node failure or application failure. A topology-spread preference reduces accidental same-node concentration but does not claim multi-zone disaster tolerance. + +## Pre-deployment verification + +Do not apply the reference unchanged. After resolving the exact image digest and cluster-specific database/DNS networking, validate the candidate against the target API server: + +```bash +kubectl apply --dry-run=server -f infrastructure/kubernetes/people-api-reference.json +``` + +Then verify, at minimum, the target cluster's admission policy, NetworkPolicy enforcement, available resource quotas, image-pull authorization, probe behaviour, disruption semantics, PostgreSQL connectivity, migration compatibility, rollback/recovery procedure and immutable release evidence. A successful dry-run is necessary evidence for the target cluster but is not sufficient release authorization. diff --git a/infrastructure/kubernetes/people-api-reference.json b/infrastructure/kubernetes/people-api-reference.json new file mode 100644 index 000000000..74fd99af9 --- /dev/null +++ b/infrastructure/kubernetes/people-api-reference.json @@ -0,0 +1,344 @@ +{ + "apiVersion": "v1", + "kind": "List", + "items": [ + { + "apiVersion": "v1", + "kind": "Namespace", + "metadata": { + "name": "orgmetra-system", + "labels": { + "pod-security.kubernetes.io/enforce": "restricted", + "pod-security.kubernetes.io/enforce-version": "v1.37", + "pod-security.kubernetes.io/audit": "restricted", + "pod-security.kubernetes.io/audit-version": "v1.37", + "pod-security.kubernetes.io/warn": "restricted", + "pod-security.kubernetes.io/warn-version": "v1.37" + } + } + }, + { + "apiVersion": "v1", + "kind": "ServiceAccount", + "metadata": { + "name": "orgmetra-people-api", + "namespace": "orgmetra-system", + "labels": { + "app.kubernetes.io/name": "orgmetra-people-api", + "app.kubernetes.io/part-of": "orgmetra" + } + }, + "automountServiceAccountToken": false + }, + { + "apiVersion": "apps/v1", + "kind": "Deployment", + "metadata": { + "name": "orgmetra-people-api", + "namespace": "orgmetra-system", + "labels": { + "app.kubernetes.io/name": "orgmetra-people-api", + "app.kubernetes.io/part-of": "orgmetra" + } + }, + "spec": { + "replicas": 2, + "minReadySeconds": 10, + "progressDeadlineSeconds": 300, + "revisionHistoryLimit": 5, + "strategy": { + "type": "RollingUpdate", + "rollingUpdate": { + "maxUnavailable": 0, + "maxSurge": 1 + } + }, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "orgmetra-people-api" + } + }, + "template": { + "metadata": { + "labels": { + "app.kubernetes.io/name": "orgmetra-people-api", + "app.kubernetes.io/part-of": "orgmetra" + } + }, + "spec": { + "serviceAccountName": "orgmetra-people-api", + "automountServiceAccountToken": false, + "hostNetwork": false, + "hostPID": false, + "hostIPC": false, + "enableServiceLinks": false, + "terminationGracePeriodSeconds": 30, + "securityContext": { + "runAsNonRoot": true, + "seccompProfile": { + "type": "RuntimeDefault" + } + }, + "topologySpreadConstraints": [ + { + "maxSkew": 1, + "topologyKey": "kubernetes.io/hostname", + "whenUnsatisfiable": "ScheduleAnyway", + "labelSelector": { + "matchLabels": { + "app.kubernetes.io/name": "orgmetra-people-api" + } + } + } + ], + "containers": [ + { + "name": "people-api", + "image": "ghcr.io/contextualwisdomlab/orgmetra-people-api@sha256:__REPLACE_WITH_VERIFIED_64_HEX_DIGEST__", + "imagePullPolicy": "IfNotPresent", + "ports": [ + { + "name": "http", + "containerPort": 8080, + "protocol": "TCP" + } + ], + "env": [ + { + "name": "PYTHONDONTWRITEBYTECODE", + "value": "1" + }, + { + "name": "PYTHONUNBUFFERED", + "value": "1" + } + ], + "securityContext": { + "privileged": false, + "allowPrivilegeEscalation": false, + "readOnlyRootFilesystem": true, + "capabilities": { + "drop": [ + "ALL" + ] + } + }, + "startupProbe": { + "httpGet": { + "path": "/health", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 5, + "timeoutSeconds": 2, + "failureThreshold": 24 + }, + "livenessProbe": { + "httpGet": { + "path": "/health", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 10, + "timeoutSeconds": 2, + "failureThreshold": 3 + }, + "readinessProbe": { + "httpGet": { + "path": "/ready", + "port": "http", + "scheme": "HTTP" + }, + "periodSeconds": 5, + "timeoutSeconds": 2, + "failureThreshold": 2 + }, + "resources": { + "requests": { + "cpu": "100m", + "memory": "128Mi", + "ephemeral-storage": "64Mi" + }, + "limits": { + "cpu": "1", + "memory": "512Mi", + "ephemeral-storage": "256Mi" + } + }, + "volumeMounts": [ + { + "name": "tmp-scratch", + "mountPath": "/tmp" + } + ] + } + ], + "volumes": [ + { + "name": "tmp-scratch", + "emptyDir": {} + } + ] + } + } + } + }, + { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "orgmetra-people-api", + "namespace": "orgmetra-system", + "labels": { + "app.kubernetes.io/name": "orgmetra-people-api", + "app.kubernetes.io/part-of": "orgmetra" + } + }, + "spec": { + "type": "ClusterIP", + "selector": { + "app.kubernetes.io/name": "orgmetra-people-api" + }, + "ports": [ + { + "name": "http", + "port": 80, + "targetPort": "http", + "protocol": "TCP" + } + ] + } + }, + { + "apiVersion": "policy/v1", + "kind": "PodDisruptionBudget", + "metadata": { + "name": "orgmetra-people-api", + "namespace": "orgmetra-system" + }, + "spec": { + "maxUnavailable": 1, + "selector": { + "matchLabels": { + "app.kubernetes.io/name": "orgmetra-people-api" + } + } + } + }, + { + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + "metadata": { + "name": "orgmetra-default-deny", + "namespace": "orgmetra-system" + }, + "spec": { + "podSelector": {}, + "policyTypes": [ + "Ingress", + "Egress" + ], + "ingress": [], + "egress": [] + } + }, + { + "apiVersion": "networking.k8s.io/v1", + "kind": "NetworkPolicy", + "metadata": { + "name": "orgmetra-people-api-access", + "namespace": "orgmetra-system" + }, + "spec": { + "podSelector": { + "matchLabels": { + "app.kubernetes.io/name": "orgmetra-people-api" + } + }, + "policyTypes": [ + "Ingress", + "Egress" + ], + "ingress": [ + { + "from": [ + { + "podSelector": { + "matchLabels": { + "orgmetra.cwl/people-api-client": "true" + } + } + } + ], + "ports": [ + { + "protocol": "TCP", + "port": 8080 + } + ] + }, + { + "from": [ + { + "ipBlock": { + "cidr": "192.0.2.0/24" + } + } + ], + "ports": [ + { + "protocol": "TCP", + "port": 8080 + } + ] + } + ], + "egress": [ + { + "to": [ + { + "podSelector": { + "matchLabels": { + "app.kubernetes.io/name": "orgmetra-postgres" + } + } + } + ], + "ports": [ + { + "protocol": "TCP", + "port": 5432 + } + ] + }, + { + "to": [ + { + "namespaceSelector": { + "matchLabels": { + "kubernetes.io/metadata.name": "kube-system" + } + }, + "podSelector": { + "matchLabels": { + "k8s-app": "kube-dns" + } + } + } + ], + "ports": [ + { + "protocol": "UDP", + "port": 53 + }, + { + "protocol": "TCP", + "port": 53 + } + ] + } + ] + } + } + ] +} diff --git a/manifest.json b/manifest.json index 97f2bab14..8d47b486a 100644 --- a/manifest.json +++ b/manifest.json @@ -1 +1,92 @@ -{"package":"orgmetra-foundation-pack","version":"0.1.0","generated_for_branch":"feat/audit-outbox-envelope","files":[{"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123},{"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105},{"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37},{"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34},{"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107},{"path":"CHANGELOG.md","sha256":"32cc4ef78d1eca557fa01731026840be01211a043eb0ada552e4e6cb9eace353","bytes":17295,"lines":76},{"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20},{"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202},{"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4},{"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81},{"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916},{"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202},{"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423},{"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234},{"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170},{"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628},{"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476},{"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448},{"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281},{"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313},{"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165},{"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76},{"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260},{"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76},{"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85},{"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70},{"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71},{"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111},{"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64},{"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28},{"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50},{"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135},{"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23},{"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40},{"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101},{"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122},{"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37},{"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77},{"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53},{"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44},{"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47},{"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30},{"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34},{"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66},{"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57},{"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55},{"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57},{"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35},{"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53},{"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59},{"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46},{"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49},{"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18},{"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69},{"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226},{"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187},{"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9},{"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160},{"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200},{"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020},{"path":"scripts/foundation-contract-core.mjs","sha256":"595e8381dbd62e97093b11eef818af5f04d6473ac592d57e3985ffbc2210d445","bytes":28173,"lines":689},{"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6},{"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34},{"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386},{"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195},{"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333},{"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357},{"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230},{"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344},{"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469},{"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370},{"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296},{"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101},{"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429},{"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377},{"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381},{"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388},{"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301},{"path":"tests/validate_repository.py","sha256":"918cf92fd18d81572e9bd5f5daa7f033c32731e2e13f0d00661d1c1de30b12a9","bytes":27291,"lines":638}]} +{ + "package": "orgmetra-foundation-pack", + "version": "0.1.0", + "generated_for_branch": "feat/kubernetes-reference-deployment", + "files": [ + {"path":".github/workflows/foundation-ci.yml","sha256":"12686a3bbd6445e6fdb202b4137dae118ddeeab1efb0c7f18ea6c8fa19d62537","bytes":4379,"lines":123}, + {"path":".github/workflows/job-analysis-api-quality.yml","sha256":"352dc78931dd94afea3e88912d38dcc4b562a004112f199f3d7a12d22b6d637a","bytes":4159,"lines":105}, + {"path":".github/workflows/kubernetes-reference-quality.yml","sha256":"fb75b3739f2b9e38ad70f3f82f882b289e3a37a8795757c98d3ea9825a2656bb","bytes":1421,"lines":46}, + {"path":".gitignore","sha256":"145fda644f5209fa1fb3e3b40c9af9258bfac6d1a634bba2520fd08fe6d77a21","bytes":375,"lines":37}, + {"path":"AGENTS.md","sha256":"28f7b7bc010a7739cfdc3e793fb5d39a0e74b842ea9c190e9a251e2d0cbc3a16","bytes":2246,"lines":34}, + {"path":"ARCHITECTURE.md","sha256":"52d68786f7359c1a50d804996021e4c70e90accd2fff6f1a27c91de1dd8df850","bytes":7864,"lines":107}, + {"path":"CHANGELOG.md","sha256":"32cc4ef78d1eca557fa01731026840be01211a043eb0ada552e4e6cb9eace353","bytes":17295,"lines":76}, + {"path":"CLAUDE.md","sha256":"add33884f466d324e20875388d103de41c6e062938a6e98727dc83a87ffe976f","bytes":1229,"lines":20}, + {"path":"LICENSE","sha256":"cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30","bytes":11358,"lines":202}, + {"path":"NOTICE","sha256":"34b4618e946bdd8d33407d6ac5279f0a0388f5e7c8f79d2e7d8c3c47d0266042","bytes":305,"lines":4}, + {"path":"README.md","sha256":"1a9fc400d26d8137ae5911488794a6d3fa915957c95f27b36a48cef0fdf823c6","bytes":3785,"lines":81}, + {"path":"database/migrations/0001_foundation_schema.sql","sha256":"ce2ae52fc66b2f99597ea5285df82c66f90caa46174fef4930d68a8b6177d0dd","bytes":38747,"lines":916}, + {"path":"database/migrations/0002_sealed_evidence_digest.sql","sha256":"93d659ca8e0e9293a83d5422d043be7b1022c5470a5b22670aa3416fa334a04c","bytes":6649,"lines":202}, + {"path":"database/migrations/0003_audit_outbox_persistence.sql","sha256":"2aa7bbb8220923ec584537c0cd46f0cba2b692d69d431f097b7df6db75235bfc","bytes":15417,"lines":423}, + {"path":"database/migrations/0004_outbox_delivery_claim.sql","sha256":"d4504acf7d58528a2a8f4f03d1584b868c8d3ba9046a007b9c2e7cfef993b2ef","bytes":9451,"lines":234}, + {"path":"database/migrations/0005_outbox_delivery_finalization.sql","sha256":"b7e8790595b288f752d6ef5cc6cbfe4e1b6712248f5b7a3a25fa60016b6a4961","bytes":6125,"lines":170}, + {"path":"database/migrations/0006_outbox_delivery_dead_letter.sql","sha256":"c1fb91cdf98169fd6684984e86cb0a14fa19c8f1226028d2346a2a069df2b3c7","bytes":24919,"lines":628}, + {"path":"database/migrations/0007_outbox_retry_exhaustion.sql","sha256":"812f50d70ca5929c7eba964d34a208aedee660d11cc7ffc09d67688c4737e0d5","bytes":19081,"lines":476}, + {"path":"database/migrations/0008_audit_outbox_review_hardening.sql","sha256":"c3713a12db9d00fdc10005df1f86c07965e9555eefad78ca67e994537a739d9b","bytes":17562,"lines":448}, + {"path":"database/migrations/0009_candidate_worker_conversion_governance.sql","sha256":"4030666629a6b8deb383b8337ead4f09d6a945969313def2577a38f31f06cda9","bytes":11537,"lines":281}, + {"path":"database/migrations/0010_validity_study_case_integrity.sql","sha256":"3f594810ac9e1a6747a2bb4838e5ce65b921cb6e3d36fcdc3ff08b4a7579ebd1","bytes":11979,"lines":313}, + {"path":"database/migrations/0011_criterion_observation_scope.sql","sha256":"f9fe7c35f1ee7b167e1c2ba75a50a84febda9a6ccf8123b4f5726f51968694f9","bytes":7444,"lines":165}, + {"path":"database/migrations/0012_people_mutation_idempotency.sql","sha256":"52dbbb9ec7f9be5291593ba88f228d7fffd736dcb99547a08c1d6cad076afb69","bytes":3162,"lines":76}, + {"path":"database/migrations/0013_job_analysis_snapshot.sql","sha256":"b6553a5a4c94c4aa9f341a474e13bbe34db63044eda2446b3ebee178995977ee","bytes":12713,"lines":260}, + {"path":"docs/API_CONTRACT.md","sha256":"63533dff785da62b89e585d742a158e2aeb05913644f2bf9fb6486f281c2e589","bytes":4555,"lines":76}, + {"path":"docs/DATA_MODEL.md","sha256":"6ad29731ae7ee7aa5bf3a2d0bfef88894a35a2550edb2be3244d6f143d76444a","bytes":13366,"lines":85}, + {"path":"docs/ERD.md","sha256":"546001aa85c4fe020e0c39d881dc860daf7f69090596666fdf9092487b0725fe","bytes":6964,"lines":70}, + {"path":"docs/OPERABILITY.md","sha256":"82b2d3e70cec371ef35e9e0f982ac40fef84351976bc04b863b81d27023d5a62","bytes":11189,"lines":71}, + {"path":"docs/PRD.md","sha256":"3ad85ae633cce0fc7a93af39b21d7a7c70bb2efa786da6b12f3c5327906e34f1","bytes":5490,"lines":111}, + {"path":"docs/SECURITY.md","sha256":"01918512d8882060e9cff0c4aa8206e0eccbdfb61cfd7f829331123c7a9fe6ac","bytes":11185,"lines":64}, + {"path":"docs/STORYBOARD.md","sha256":"6e4ffb0eb03a80343f50d363ffc43b34da9348a44232dd947a9ff416ea92a3d2","bytes":1342,"lines":28}, + {"path":"docs/STORYBOOK.md","sha256":"82f79029b3c2b7a45393bad5ba8fabe61014d4b6149c7d4e73f70ba447f885e9","bytes":1389,"lines":50}, + {"path":"docs/TEST_STRATEGY.md","sha256":"d0a0bc3b54ed0fc7973747987f1afb117d6144c390b51ed9370eb571972a33f8","bytes":16534,"lines":135}, + {"path":"docs/THREAT_MODEL.md","sha256":"f314f375c2e41252536de224c7bc7e4a10ab8f340cb86642724e7399e32f4252","bytes":6736,"lines":23}, + {"path":"docs/TRACEABILITY.md","sha256":"dbf6fd91375ea28e05456d2a0c9ba629506cbac6f52f5dfda61ae68db2395f7e","bytes":11462,"lines":40}, + {"path":"docs/TRD.md","sha256":"23697d88a4882698e1a2782b7da3f2ccd0d3cd2d6d1bffe89b6597dc16851077","bytes":9064,"lines":101}, + {"path":"docs/UML.md","sha256":"fe67c37aa88e5814ceb2db7e8f7d8d85ca27a994802efbb7c75164b387adf0a9","bytes":5528,"lines":122}, + {"path":"docs/USER_STORIES.md","sha256":"5535b39d8c71a36c81f78e2d6dbd90a2d32e6541790f0d28f6dd4baf3ea7b45f","bytes":2670,"lines":37}, + {"path":"docs/WIREFRAMES.md","sha256":"b03aa6419aeaf5d42a5698c4d43a434c1633b7ac6fd0b0bd0cda979077adc56e","bytes":2005,"lines":77}, + {"path":"docs/adr/0001-orgmetra-authoritative-hris-record.md","sha256":"0f8055b73c63d3130321415ad53233588ff952aabd1a88952b39c71747253572","bytes":6108,"lines":53}, + {"path":"docs/adr/0002-federated-cwl-integration-boundaries.md","sha256":"b77165f2aacfa6f4fde994baf77d5879c6da3e8dae4fd2db0ed912d60ae9b3b2","bytes":4072,"lines":44}, + {"path":"docs/adr/0003-bitemporal-hris-data-contract.md","sha256":"d7f2660616622c1a7994b28aa66d99d13836bcf755735595f9609a41282ab799","bytes":4453,"lines":47}, + {"path":"docs/adr/0004-employment-position-version-and-assignment-binding.md","sha256":"fee89e700414abe0b1cffec2acc687e5e014634db8f5ef9e8a92abba5c3cf182","bytes":1872,"lines":30}, + {"path":"docs/adr/0005-exclusive-employment-and-staffable-seats.md","sha256":"10f0eb409f4fa32d2c5bed2d583d8b43be8e61b5cbef0e927e5bebb5f5c8f85b","bytes":2091,"lines":34}, + {"path":"docs/adr/0006-governed-audit-outbox-envelope.md","sha256":"827298ddd997b47f78a89e89911ad8ea72e517b7714303637f0329b8cb52cabd","bytes":14100,"lines":66}, + {"path":"docs/adr/0007-governed-job-analysis-evidence.md","sha256":"953c6d2b9864a78b461b576092ec3f198f0b76709eaaaf7d0ed0182f95182c52","bytes":5653,"lines":57}, + {"path":"docs/adr/0008-purpose-bound-pii-authorization.md","sha256":"c5157d3bc58f3d8d29e03104dd15eb2911cc1bb66e2c92a935b26d7164648dc7","bytes":5988,"lines":55}, + {"path":"docs/adr/0009-performance-criterion-observation-scope.md","sha256":"1ac10bb2747b0a5b4d62f627825cfd7f978f3fa88d7575bffc23d56371240a64","bytes":7057,"lines":57}, + {"path":"docs/adr/0010-naruon-calendar-intent-boundary.md","sha256":"3e1050a964cc4ed76a1a0cf1e699ae5080acf8c9336f0decdd6d5229359db3c9","bytes":3917,"lines":35}, + {"path":"docs/adr/0011-bitemporal-workforce-composition.md","sha256":"1656ef8b57c836ef7936a8e9cb6a824681eb7563157a1ab0a29deb25849a457b","bytes":5568,"lines":53}, + {"path":"docs/adr/0012-governed-migration-handoff.md","sha256":"713855d670001d3964ecb36cc653830502fb1d82a58b9e39f564b6992dd2bd80","bytes":5965,"lines":59}, + {"path":"docs/adr/0013-governed-requisition-review-packet.md","sha256":"70bf2cbdf903a8793d6d8bc116a08331931090118341f42010236e09c6cc1802","bytes":4693,"lines":46}, + {"path":"docs/adr/0014-job-analysis-snapshot-persistence.md","sha256":"a7ab6fee50aaa63f7f407516a4cb39885faeb0fc6e5035ee8fc352ed73430105","bytes":5365,"lines":49}, + {"path":"docs/adr/README.md","sha256":"f3b3b5ed3b3b31a40a0a3696abf0065e3c25879b6be50077f38ffae742b9d002","bytes":1838,"lines":18}, + {"path":"docs/doctoring/REFERENCES.md","sha256":"929f7ee36df16279f028f726fcf039982180deb377746fe3804f3c0d090778d5","bytes":6352,"lines":69}, + {"path":"docs/doctoring/kubernetes-reference-deployment-references.md","sha256":"49567f7a50c6736d51143366c38104ce56ca360f9446947dbb44e6ecdd2fa78d","bytes":3253,"lines":26}, + {"path":"docs/superpowers/plans/2026-08-15-orgmetra-foundation-implementation-plan.md","sha256":"b64f21abb19373e780db8b9e64deb8ba9a6219ccf9625a651f25407b8691fcbd","bytes":8227,"lines":226}, + {"path":"docs/superpowers/specs/2026-08-15-orgmetra-foundation-design.md","sha256":"4a0e1a7943e40d12bd3082db3757045b4085e5a089fea7bc0d8a1565ffcbcf1d","bytes":6237,"lines":187}, + {"path":"docs/traceability/kubernetes-reference-deployment.md","sha256":"417fdf56e4a3304d45d9c455faf083f9e1a03e8bf3c97b39e5fbaf83253755c8","bytes":4961,"lines":37}, + {"path":"infrastructure/kubernetes/README.md","sha256":"1561e352c5062da262ae52c443912c583b2bc7d2f113cbfb99187fbe2643f56b","bytes":6097,"lines":58}, + {"path":"infrastructure/kubernetes/people-api-reference.json","sha256":"e11a6df307a1640867753fd323d2f10de147cd1990f7569822fd164157538098","bytes":9302,"lines":344}, + {"path":"package.json","sha256":"59ae9e3e67c3fba9320cb18439692395cdfd16ae5c24e3c4cf30d77d63ebabb5","bytes":388,"lines":9}, + {"path":"packages/hris-kernel/src/orgmetra_hris_kernel/audit.py","sha256":"3e5b7190cf857dc8c1fc7e898cef303060f34aabee6c27a9034d4d9650e33190","bytes":7707,"lines":160}, + {"path":"packages/hris-kernel/tests/test_audit_outbox.py","sha256":"5928dd7b97fe38d6b7472ce62966437e339058a59c3b301a93a7b5c05432b40c","bytes":7556,"lines":200}, + {"path":"schemas/openapi.yaml","sha256":"09c1e43486779198574fe31b8bcabbd1c1f74beec7bf86245ae578061619838f","bytes":29503,"lines":1020}, + {"path":"scripts/foundation-contract-core.mjs","sha256":"13bb19e3a545368259e0bf0822ea34a9c9b51f41fd4d559ca88b8716c8fed83e","bytes":28492,"lines":695}, + {"path":"scripts/foundation-contract.mjs","sha256":"5242dcdbe0935775edf074462c82600e9bc4927d9fdc50c47727af915fd4b23a","bytes":218,"lines":6}, + {"path":"tests/dispatcher-inventory.test.mjs","sha256":"09f5e64410e6b7a26bf8d6ce61c50b737da2ea85d955f91eba63aa21f1537261","bytes":1597,"lines":34}, + {"path":"tests/foundation-contract.test.mjs","sha256":"960306fd7cda7b982a52c4428a432d10a4f570430a5d39fb23aeca0b2ede0615","bytes":14860,"lines":386}, + {"path":"tests/kubernetes-reference.test.mjs","sha256":"83c2636d12c73f4b05e9fb0a9912a345d86db4a5a88eb1278b7032c14eb95163","bytes":9938,"lines":275}, + {"path":"tests/openapi-contract.test.mjs","sha256":"80c1610ef1c189fa325e55389501e0e51531ddf61ee335bb94d9cb3aa55a9fdc","bytes":6438,"lines":195}, + {"path":"tests/test_audit_outbox_hardening_postgres.sh","sha256":"518ba2f37ba6292943e5abe22c2599452b2f031a42e453b2493aedf8714421a0","bytes":13396,"lines":333}, + {"path":"tests/test_audit_outbox_postgres.sh","sha256":"e57a04920a0ba97fa6a06752d15ea150016ab8d44099e998c5c4f4067592b4d2","bytes":13443,"lines":357}, + {"path":"tests/test_bitemporal_postgres.sh","sha256":"7684b8c2ff52c044c081135515bd5aabbfd00e2daad0d471b0868701af2df6cc","bytes":8209,"lines":230}, + {"path":"tests/test_candidate_worker_conversion_postgres.sh","sha256":"681cb74d6cfa859ed92c6c2439881ea20c430ef8df94ec662e2807761a377f90","bytes":14673,"lines":344}, + {"path":"tests/test_criterion_observation_scope_postgres.sh","sha256":"0ee9539ee57f840c27d08009f7868cdc8662669df78a01dbc8be39216b8f1a3d","bytes":17811,"lines":469}, + {"path":"tests/test_evidence_sealing_postgres.sh","sha256":"57d16b632a0c60ffdcb4842ceb1cfe25d19c54cefeeefb622ff4fa6e83441ad7","bytes":11349,"lines":370}, + {"path":"tests/test_job_analysis_snapshot_postgres.sh","sha256":"ca9c323a1dd68cfc520277efbbb7495e37fb3ca027890928c8624e5b4f57403f","bytes":13542,"lines":296}, + {"path":"tests/test_operational_uuid_postgres.sh","sha256":"7378f98f0d4b3000e8ea641d8701f1540dbad71410b3637d81d799969e0f6ff7","bytes":3346,"lines":101}, + {"path":"tests/test_outbox_claim_postgres.sh","sha256":"1027806d436ebfe34e108c25b6a4001f43b9550f1d70057c6c0d7974323b0c9b","bytes":14817,"lines":429}, + {"path":"tests/test_outbox_dead_letter_postgres.sh","sha256":"0d728d578e64252e6079f2d141ddaa7fa9cfbf9784e625832273596d69a6e13d","bytes":14008,"lines":377}, + {"path":"tests/test_people_mutation_idempotency_postgres.sh","sha256":"3f57e12f80bd1b034c9aac54b669d8530106e3e26b3795689671fb53807b3cd5","bytes":16191,"lines":381}, + {"path":"tests/test_tenant_isolation_postgres.sh","sha256":"dd649435ef8ab9e57f0609c101917e36656a6d40d63de9bcdbdac23d764f6c3a","bytes":15134,"lines":388}, + {"path":"tests/test_validity_study_case_postgres.sh","sha256":"0070ad58300323c7f9900c5645e0df3106b36ccd245ae686e982c2fd6fa4dc02","bytes":14708,"lines":301}, + {"path":"tests/validate_repository.py","sha256":"9baf354b4728cd0087480e7d04c189b89c41f4b37e3e3a2ea2bda9080803287f","bytes":27652,"lines":644} + ] +} \ No newline at end of file diff --git a/scripts/foundation-contract-core.mjs b/scripts/foundation-contract-core.mjs index 1e9fb267c..45fcb31ea 100644 --- a/scripts/foundation-contract-core.mjs +++ b/scripts/foundation-contract-core.mjs @@ -71,6 +71,12 @@ export const REQUIRED_FILES = Object.freeze([ 'packages/hris-kernel/src/orgmetra_hris_kernel/audit.py', 'packages/hris-kernel/tests/test_audit_outbox.py', 'schemas/openapi.yaml', + '.github/workflows/kubernetes-reference-quality.yml', + 'docs/doctoring/kubernetes-reference-deployment-references.md', + 'docs/traceability/kubernetes-reference-deployment.md', + 'infrastructure/kubernetes/README.md', + 'infrastructure/kubernetes/people-api-reference.json', + 'tests/kubernetes-reference.test.mjs', 'scripts/foundation-contract-core.mjs', 'scripts/foundation-contract.mjs', 'tests/dispatcher-inventory.test.mjs', diff --git a/tests/kubernetes-reference.test.mjs b/tests/kubernetes-reference.test.mjs new file mode 100644 index 000000000..c51e08a42 --- /dev/null +++ b/tests/kubernetes-reference.test.mjs @@ -0,0 +1,297 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const REFERENCE_PATH = path.join( + ROOT, + "infrastructure", + "kubernetes", + "people-api-reference.json", +); +const README_PATH = path.join(ROOT, "infrastructure", "kubernetes", "README.md"); +const IMAGE_SENTINEL = + "ghcr.io/contextualwisdomlab/orgmetra-people-api@sha256:__REPLACE_WITH_VERIFIED_64_HEX_DIGEST__"; + +function referenceDocument() { + return JSON.parse(fs.readFileSync(REFERENCE_PATH, "utf8")); +} + +function resource(document, kind, name) { + const match = document.items.find( + (item) => item.kind === kind && item.metadata?.name === name, + ); + assert.ok(match, `missing ${kind}/${name}`); + return match; +} + +function peopleContainer(document) { + const deployment = resource(document, "Deployment", "orgmetra-people-api"); + assert.equal(deployment.spec.template.spec.containers.length, 1); + return deployment.spec.template.spec.containers[0]; +} + +// RFC 5737 TEST-NET-1 placeholder: operators MUST replace it with the exact +// node CIDR their kubelet probes originate from before server-side apply. +const PROBE_CIDR_PLACEHOLDER = "192.0.2.0/24"; + +test("kubelet probe ingress is explicitly permitted", () => { + const document = referenceDocument(); + const policy = resource(document, "NetworkPolicy", "orgmetra-people-api-access"); + const probeRules = (policy.spec.ingress ?? []).filter((rule) => + (rule.from ?? []).some((source) => "ipBlock" in source), + ); + assert.equal(probeRules.length, 1); + assert.deepEqual(probeRules[0].from, [{ ipBlock: { cidr: PROBE_CIDR_PLACEHOLDER } }]); + assert.deepEqual(probeRules[0].ports, [{ protocol: "TCP", port: 8080 }]); +}); + +test("read-only root filesystem has a dedicated writable tmp scratch volume", () => { + const document = referenceDocument(); + const deployment = resource(document, "Deployment", "orgmetra-people-api"); + const podSpec = deployment.spec.template.spec; + const scratchVolumes = podSpec.volumes.filter( + (volume) => volume.name === "tmp-scratch", + ); + assert.equal(scratchVolumes.length, 1); + assert.deepEqual(scratchVolumes[0].emptyDir, {}); + for (const volume of podSpec.volumes) { + assert.equal("hostPath" in volume, false, "hostPath volumes are forbidden"); + } + const container = peopleContainer(document); + const mounts = container.volumeMounts.filter( + (mount) => mount.mountPath === "/tmp", + ); + assert.equal(mounts.length, 1); + assert.deepEqual(mounts[0], { name: "tmp-scratch", mountPath: "/tmp" }); +}); + +test("reference contains the bounded deployment resource set", () => { + const document = referenceDocument(); + assert.equal(document.apiVersion, "v1"); + assert.equal(document.kind, "List"); + assert.equal(document.items.length, 7); + + resource(document, "Namespace", "orgmetra-system"); + resource(document, "ServiceAccount", "orgmetra-people-api"); + resource(document, "Deployment", "orgmetra-people-api"); + resource(document, "Service", "orgmetra-people-api"); + resource(document, "PodDisruptionBudget", "orgmetra-people-api"); + resource(document, "NetworkPolicy", "orgmetra-default-deny"); + resource(document, "NetworkPolicy", "orgmetra-people-api-access"); +}); + +test("namespace and workload align with restricted pod-security intent", () => { + const document = referenceDocument(); + const namespace = resource(document, "Namespace", "orgmetra-system"); + assert.equal( + namespace.metadata.labels["pod-security.kubernetes.io/enforce"], + "restricted", + ); + assert.equal( + namespace.metadata.labels["pod-security.kubernetes.io/audit"], + "restricted", + ); + assert.equal( + namespace.metadata.labels["pod-security.kubernetes.io/warn"], + "restricted", + ); + + const serviceAccount = resource( + document, + "ServiceAccount", + "orgmetra-people-api", + ); + assert.equal(serviceAccount.automountServiceAccountToken, false); + + const deployment = resource(document, "Deployment", "orgmetra-people-api"); + const podSpec = deployment.spec.template.spec; + assert.equal(podSpec.automountServiceAccountToken, false); + assert.equal(podSpec.serviceAccountName, "orgmetra-people-api"); + assert.equal(podSpec.hostNetwork, false); + assert.equal(podSpec.hostPID, false); + assert.equal(podSpec.hostIPC, false); + assert.equal(podSpec.enableServiceLinks, false); + assert.equal(podSpec.securityContext.runAsNonRoot, true); + assert.deepEqual(podSpec.securityContext.seccompProfile, { + type: "RuntimeDefault", + }); + + for (const volume of podSpec.volumes ?? []) { + assert.equal("hostPath" in volume, false, "hostPath volumes are forbidden"); + } + + const container = peopleContainer(document); + assert.equal(container.image, IMAGE_SENTINEL); + assert.equal(container.imagePullPolicy, "IfNotPresent"); + assert.equal(container.securityContext.privileged, false); + assert.equal(container.securityContext.allowPrivilegeEscalation, false); + assert.equal(container.securityContext.readOnlyRootFilesystem, true); + assert.deepEqual(container.securityContext.capabilities, { drop: ["ALL"] }); + assert.equal("hostPort" in container.ports[0], false); +}); + +test("pod-security policy behavior is pinned to the authored Kubernetes minor", () => { + const document = referenceDocument(); + const namespace = resource(document, "Namespace", "orgmetra-system"); + for (const mode of ["enforce", "audit", "warn"]) { + assert.equal( + namespace.metadata.labels[`pod-security.kubernetes.io/${mode}-version`], + "v1.37", + `${mode} Pod Security policy must not drift with an implicit latest version`, + ); + } +}); + +test("health probes preserve liveness/readiness separation", () => { + const document = referenceDocument(); + const container = peopleContainer(document); + + assert.deepEqual(container.startupProbe.httpGet, { + path: "/health", + port: "http", + scheme: "HTTP", + }); + assert.equal(container.startupProbe.periodSeconds, 5); + assert.equal(container.startupProbe.failureThreshold, 24); + + assert.deepEqual(container.livenessProbe.httpGet, { + path: "/health", + port: "http", + scheme: "HTTP", + }); + assert.equal(container.livenessProbe.periodSeconds, 10); + assert.equal(container.livenessProbe.failureThreshold, 3); + + assert.deepEqual(container.readinessProbe.httpGet, { + path: "/ready", + port: "http", + scheme: "HTTP", + }); + assert.equal(container.readinessProbe.periodSeconds, 5); + assert.equal(container.readinessProbe.failureThreshold, 2); +}); + +test("deployment bounds resources and voluntary disruption", () => { + const document = referenceDocument(); + const deployment = resource(document, "Deployment", "orgmetra-people-api"); + const container = peopleContainer(document); + + assert.equal(deployment.spec.replicas, 2); + assert.equal(deployment.spec.minReadySeconds, 10); + assert.equal(deployment.spec.progressDeadlineSeconds, 300); + assert.equal(deployment.spec.revisionHistoryLimit, 5); + assert.deepEqual(deployment.spec.strategy, { + type: "RollingUpdate", + rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }, + }); + assert.deepEqual(container.resources, { + requests: { cpu: "100m", memory: "128Mi", "ephemeral-storage": "64Mi" }, + limits: { cpu: "1", memory: "512Mi", "ephemeral-storage": "256Mi" }, + }); + + const pdb = resource(document, "PodDisruptionBudget", "orgmetra-people-api"); + assert.equal(pdb.apiVersion, "policy/v1"); + assert.equal(pdb.spec.maxUnavailable, 1); + assert.deepEqual(pdb.spec.selector, { + matchLabels: { "app.kubernetes.io/name": "orgmetra-people-api" }, + }); +}); + +test("network policy is default-deny with explicit People API flows", () => { + const document = referenceDocument(); + const deny = resource(document, "NetworkPolicy", "orgmetra-default-deny"); + assert.deepEqual(deny.spec.podSelector, {}); + assert.deepEqual(deny.spec.policyTypes, ["Ingress", "Egress"]); + assert.deepEqual(deny.spec.ingress, []); + assert.deepEqual(deny.spec.egress, []); + + const access = resource( + document, + "NetworkPolicy", + "orgmetra-people-api-access", + ); + assert.deepEqual(access.spec.policyTypes, ["Ingress", "Egress"]); + assert.deepEqual(access.spec.podSelector, { + matchLabels: { "app.kubernetes.io/name": "orgmetra-people-api" }, + }); + + assert.deepEqual(access.spec.ingress, [ + { + from: [ + { + podSelector: { + matchLabels: { "orgmetra.cwl/people-api-client": "true" }, + }, + }, + ], + ports: [{ protocol: "TCP", port: 8080 }], + }, + { + from: [{ ipBlock: { cidr: PROBE_CIDR_PLACEHOLDER } }], + ports: [{ protocol: "TCP", port: 8080 }], + }, + ]); + + assert.deepEqual(access.spec.egress, [ + { + to: [ + { + podSelector: { + matchLabels: { "app.kubernetes.io/name": "orgmetra-postgres" }, + }, + }, + ], + ports: [{ protocol: "TCP", port: 5432 }], + }, + { + to: [ + { + namespaceSelector: { + matchLabels: { "kubernetes.io/metadata.name": "kube-system" }, + }, + podSelector: { matchLabels: { "k8s-app": "kube-dns" } }, + }, + ], + ports: [ + { protocol: "UDP", port: 53 }, + { protocol: "TCP", port: 53 }, + ], + }, + ]); +}); + +test("reference documentation keeps digest resolution and database egress fail-closed", () => { + const text = fs.readFileSync(README_PATH, "utf8"); + assert.match(text, /verified 64-character lowercase SHA-256 image digest/i); + assert.match(text, /does not authorize a release/i); + assert.match(text, /managed PostgreSQL/i); + assert.match(text, /replace the database egress rule/i); + assert.match(text, /NetworkPolicy-capable CNI/i); + assert.match(text, /kubectl apply --dry-run=server/i); + assert.match(text, /pod-security\.kubernetes\.io\/enforce=restricted/i); + assert.match(text, /fresh target cluster/i); + assert.match(text, /temporary validation namespace/i); + assert.match(text, /kubectl create namespace/i); + assert.match(text, /kubectl delete namespace/i); +}); + +test("canonical buyer-facing docs track the Kubernetes reference deployment boundary", () => { + for (const relativePath of [ + "ARCHITECTURE.md", + "CHANGELOG.md", + "docs/OPERABILITY.md", + "docs/SECURITY.md", + "docs/TEST_STRATEGY.md", + "docs/TRACEABILITY.md", + ]) { + const text = fs.readFileSync(path.join(ROOT, relativePath), "utf8"); + assert.match( + text, + /infrastructure\/kubernetes\/people-api-reference\.json/i, + `${relativePath} must identify the governed Kubernetes reference artifact`, + ); + } +}); diff --git a/tests/validate_repository.py b/tests/validate_repository.py index fe0a329ff..4d40f7b44 100644 --- a/tests/validate_repository.py +++ b/tests/validate_repository.py @@ -74,6 +74,12 @@ "packages/hris-kernel/src/orgmetra_hris_kernel/audit.py", "packages/hris-kernel/tests/test_audit_outbox.py", "schemas/openapi.yaml", + ".github/workflows/kubernetes-reference-quality.yml", + "docs/doctoring/kubernetes-reference-deployment-references.md", + "docs/traceability/kubernetes-reference-deployment.md", + "infrastructure/kubernetes/README.md", + "infrastructure/kubernetes/people-api-reference.json", + "tests/kubernetes-reference.test.mjs", "scripts/foundation-contract-core.mjs", "scripts/foundation-contract.mjs", "tests/dispatcher-inventory.test.mjs", @@ -139,7 +145,7 @@ def _expected_manifest_document() -> dict[str, Any]: return { "package": "orgmetra-foundation-pack", "version": "0.1.0", - "generated_for_branch": "feat/audit-outbox-envelope", + "generated_for_branch": "feat/kubernetes-reference-deployment", "files": files, } @@ -153,10 +159,10 @@ def _manifest_entries() -> dict[str, dict[str, Any]]: if not isinstance(manifest, dict) or not isinstance(manifest.get("files"), list): _fail("manifest.json must contain a files array") - if manifest.get("generated_for_branch") != "feat/audit-outbox-envelope": + if manifest.get("generated_for_branch") != "feat/kubernetes-reference-deployment": _fail( "manifest generated_for_branch must identify the active generation branch " - "feat/audit-outbox-envelope" + "feat/kubernetes-reference-deployment" ) entries: dict[str, dict[str, Any]] = {}