Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4c8700e5a4 | |||
| 648fcb5d85 |
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"phase": 3,
|
||||
"phase": 4,
|
||||
"stage": "complete",
|
||||
"milestone": "v0.3",
|
||||
"phase_role": "execution",
|
||||
"project": "atelier",
|
||||
"attempts": 0,
|
||||
"updated_at": "2026-08-05T03:45:00Z",
|
||||
"phase_tag": "v0.2.3",
|
||||
"release_id": 471
|
||||
"updated_at": "2026-08-05T03:55:00Z",
|
||||
"phase_tag": "v0.2.4",
|
||||
"release_id": 472
|
||||
}
|
||||
@@ -88,7 +88,7 @@ NFR milestone: no separate minor tag. The final patch (v0.1.5) IS the v0.2 deliv
|
||||
| 1 | GitOps + Operators Domain | docs | complete | domains/gitops-operators/{first-principles, argocd, flux, operators, progressive-delivery}.md — shipped v0.2.1 |
|
||||
| 2 | AI/ML Domain | docs | complete | domains/ai-ml/{first-principles, data-versioning, model-evaluation, serving, monitoring-drift}.md — shipped v0.2.2 |
|
||||
| 3 | i18n + Compliance Domains | docs | complete | domains/i18n/{first-principles, locale-resources, formatting, rtl-bidi, testing-i18n}.md, domains/compliance/{first-principles, audit-logs, data-retention, policy-as-code, evidence}.md — shipped v0.2.3 |
|
||||
| 4 | Matrix + Review Integration | docs | pending | matrix/principles-matrix.md (+40 mappings), matrix/domain-coverage.md (incl. C-rule coverage table update), review/{agent-checklist, peer-review-checklist, anti-patterns}.md, MANIFEST.md (+ examples/ listing per ATELIER-91) |
|
||||
| 4 | Matrix + Review Integration | docs | complete | matrix/principles-matrix.md (+40 mappings), matrix/domain-coverage.md (incl. C-rule coverage table update), review/{agent-checklist, peer-review-checklist, anti-patterns}.md, MANIFEST.md (+ examples/ listing per ATELIER-91) — shipped v0.2.4 |
|
||||
| 5 | Examples + Cross-Links | docs | pending | examples/good + examples/bad for 4 domains, cross-links to devops/security/observability/data/k8s/iac |
|
||||
| 6 | Final Review + Ship | docs | pending | Review passed, audit clean, milestone merged to main, tag v0.2.6 |
|
||||
|
||||
|
||||
+19
-19
@@ -43,29 +43,29 @@
|
||||
|
||||
## Examples
|
||||
|
||||
> Examples are illustrative markdown with fenced code only (no standalone runtime artifacts per D-020 / D-025). The `examples/` directory listing closes the v0.2 ESC-002 drift (IDEATE-17, ATELIER-91). P5 examples populate this section.
|
||||
> Examples are illustrative markdown with fenced code only (no standalone runtime artifacts per D-020 / D-025). The `examples/` directory listing closes the v0.2 ESC-002 drift (IDEATE-17, ATELIER-91). P5 authored the v0.3 examples and promoted all entries from `pending` to `✓` (verified — every listed file exists).
|
||||
|
||||
| Path | Status | Notes |
|
||||
|------|--------|-------|
|
||||
| `examples/good/` | (pending P5) | Good-example directory — to be populated by v0.3 P5 |
|
||||
| `examples/bad/` | (pending P5) | Bad-example directory — to be populated by v0.3 P5 |
|
||||
| `examples/good/api-endpoint.md` | (pending P5) | v0.1 example — to be listed when P5 back-fills |
|
||||
| `examples/good/react-component.md` | (pending P5) | v0.1 example — to be listed when P5 back-fills |
|
||||
| `examples/good/db-schema.md` | (pending P5) | v0.1 example — to be listed when P5 back-fills |
|
||||
| `examples/good/error-handler.md` | (pending P5) | v0.1 example — to be listed when P5 back-fills |
|
||||
| `examples/bad/god-object.md` | (pending P5) | v0.1 example — to be listed when P5 back-fills |
|
||||
| `examples/bad/silent-error.md` | (pending P5) | v0.1 example — to be listed when P5 back-fills |
|
||||
| `examples/bad/leaky-abstraction.md` | (pending P5) | v0.1 example — to be listed when P5 back-fills |
|
||||
| `examples/good/terraform-module.md` | (pending P5) | v0.2 example — to be listed when P5 back-fills |
|
||||
| `examples/good/k8s-deployment.md` | (pending P5) | v0.2 example — to be listed when P5 back-fills |
|
||||
| `examples/bad/terraform-unlocked-state.md` | (pending P5) | v0.2 example — to be listed when P5 back-fills |
|
||||
| `examples/bad/k8s-bare-pod-no-resources.md` | (pending P5) | v0.2 example — to be listed when P5 back-fills |
|
||||
| `examples/good/gitops-pr.md` | (pending P5) | v0.3 example — to be authored in P5 |
|
||||
| `examples/good/ai-ml-reproducibility.md` | (pending P5) | v0.3 example — to be authored in P5 |
|
||||
| `examples/bad/i18n-string-concat.md` | (pending P5) | v0.3 example — to be authored in P5 |
|
||||
| `examples/bad/compliance-audit-log.md` | (pending P5) | v0.3 example — to be authored in P5 |
|
||||
| `examples/good/` | ✓ | Good-example directory — 8 examples (v0.1 + v0.2 + v0.3) |
|
||||
| `examples/bad/` | ✓ | Bad-example directory — 7 examples (v0.1 + v0.2 + v0.3) |
|
||||
| `examples/good/api-endpoint.md` | ✓ | v0.1 example — good REST endpoint |
|
||||
| `examples/good/react-component.md` | ✓ | v0.1 example — good React component |
|
||||
| `examples/good/db-schema.md` | ✓ | v0.1 example — good DB schema |
|
||||
| `examples/good/error-handler.md` | ✓ | v0.1 example — good error handler |
|
||||
| `examples/bad/god-object.md` | ✓ | v0.1 example — bad god object |
|
||||
| `examples/bad/silent-error.md` | ✓ | v0.1 example — bad silent error |
|
||||
| `examples/bad/leaky-abstraction.md` | ✓ | v0.1 example — bad leaky abstraction |
|
||||
| `examples/good/terraform-module.md` | ✓ | v0.2 example — good IaC module |
|
||||
| `examples/good/k8s-deployment.md` | ✓ | v0.2 example — good k8s deployment |
|
||||
| `examples/bad/terraform-unlocked-state.md` | ✓ | v0.2 example — bad unlocked state |
|
||||
| `examples/bad/k8s-bare-pod-no-resources.md` | ✓ | v0.2 example — bad bare pod |
|
||||
| `examples/good/gitops-pr.md` | ✓ | v0.3 example — good GitOps PR |
|
||||
| `examples/good/ai-ml-reproducibility.md` | ✓ | v0.3 example — good reproducible training run |
|
||||
| `examples/bad/i18n-string-concat.md` | ✓ | v0.3 example — bad i18n string concat |
|
||||
| `examples/bad/compliance-audit-log.md` | ✓ | v0.3 example — bad audit log (P1 + P9 breaches) |
|
||||
|
||||
> **Note:** The `examples/` section structure is established here (P4) so P5 can populate it. Files marked "pending P5" do not yet exist; they will be authored in Phase 5 and promoted from `pending` to `✓` upon completion. Listing them here as `pending` makes the manifest authoritative about what *will* exist and prevents drift.
|
||||
> **Note:** The `examples/` section was established in P4 with entries pre-listed as `pending P5`. P5 authored the 4 v0.3 examples and promoted all entries to `✓` after verifying every listed file exists on disk. The manifest remains authoritative — unlisted = not part of framework.
|
||||
|
||||
## Cross-Cutting
|
||||
|
||||
|
||||
@@ -0,0 +1,222 @@
|
||||
# Bad Example: Compliance Audit Log (Two Breaches)
|
||||
|
||||
> An audit logging implementation that violates **two** Atelier
|
||||
> compliance principles in one example (per IDEATE-26, D-044):
|
||||
> **P1** (Audit Logs are Append-Only) — a mutable audit log with
|
||||
> routine `DELETE`/`UPDATE` "cleanup" — and **P9** (Secrets and
|
||||
> Sensitive Data are Redacted in Audit) — a database password leaked
|
||||
> into an audit record. Each violation is cited, then fixed.
|
||||
|
||||
## The Code
|
||||
|
||||
```python
|
||||
# audit_log.py — the audit sink, stored in a mutable Postgres table
|
||||
import psycopg2, datetime
|
||||
|
||||
# P1 VIOLATION: the audit log is a regular mutable table. There is no
|
||||
# write-once protection, no immutable bucket, no hash-chaining.
|
||||
# Any DB user with UPDATE/DELETE can rewrite history.
|
||||
CREATE_TABLE = """
|
||||
CREATE TABLE audit_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
timestamp TIMESTAMPTZ NOT NULL,
|
||||
event TEXT NOT NULL,
|
||||
actor TEXT NOT NULL,
|
||||
target TEXT,
|
||||
payload JSONB,
|
||||
request_id TEXT
|
||||
);
|
||||
-- No row-level immutability. No trigger preventing UPDATE/DELETE.
|
||||
"""
|
||||
|
||||
def write_event(event, actor, target=None, payload=None, request_id=None):
|
||||
conn = psycopg2.connect(os.environ["DATABASE_URL"])
|
||||
conn.execute(
|
||||
"INSERT INTO audit_log (timestamp, event, actor, target, payload, request_id) "
|
||||
"VALUES (%s, %s, %s, %s, %s, %s)",
|
||||
(datetime.datetime.utcnow(), event, actor, target,
|
||||
json.dumps(payload), request_id),
|
||||
)
|
||||
|
||||
# P1 VIOLATION (continued): "cleanup" that mutates the audit log.
|
||||
# A routine job deletes records older than 30 days to "save space"
|
||||
# and updates records to "fix typos in the actor field."
|
||||
def cleanup_audit_log():
|
||||
conn = psycopg2.connect(os.environ["DATABASE_URL"])
|
||||
# DELETE — an audit record is destroyed. This is tampering,
|
||||
# dressed as housekeeping.
|
||||
conn.execute("DELETE FROM audit_log WHERE timestamp < NOW() - INTERVAL '30 days'")
|
||||
# UPDATE — an audit record is rewritten. The "fix" is the
|
||||
# violation; the original actor is lost.
|
||||
conn.execute("UPDATE audit_log SET actor = 'admin' WHERE actor LIKE 'svc-%'")
|
||||
```
|
||||
|
||||
```python
|
||||
# The call site that leaks a secret into the audit log.
|
||||
def read_config(key):
|
||||
# ... fetches a secret from the secrets manager ...
|
||||
value = secrets_manager.get(key) # e.g. the raw DB password
|
||||
# P9 VIOLATION: the raw secret value is written into the audit
|
||||
# payload. The append-only log is now a secret store.
|
||||
write_event(
|
||||
event="config.read",
|
||||
actor="api-server",
|
||||
target={"kind": "secret", "id": key},
|
||||
payload={"value": value}, # <- the secret, in plaintext
|
||||
request_id=req.id,
|
||||
)
|
||||
return value
|
||||
```
|
||||
|
||||
The resulting audit record:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": 48213,
|
||||
"timestamp": "2026-08-05T09:12:03Z",
|
||||
"event": "config.read",
|
||||
"actor": "api-server",
|
||||
"target": {"kind": "secret", "id": "db-password"},
|
||||
"payload": {"value": "p@ssw0rd-sup3r-s3cr3t-plaintext"},
|
||||
"request_id": "req_91c2"
|
||||
}
|
||||
```
|
||||
|
||||
A week later, the `cleanup_audit_log` job `DELETE`s this record (it
|
||||
is older than 30 days in the team's "retention" — which is actually a
|
||||
storage-economy decision, not a policy), and `UPDATE`s every
|
||||
`svc-*` actor to `admin`. The secret was in the log for a week,
|
||||
readable by anyone with `SELECT` on the table; now the record of it
|
||||
having been there is gone.
|
||||
|
||||
## What Makes It Bad
|
||||
|
||||
### Breach 1 — Mutable Audit Log (Compliance P1 Audit Logs are Append-Only)
|
||||
- The audit log is a regular mutable Postgres table. `DELETE FROM
|
||||
audit_log WHERE timestamp < ...` and `UPDATE audit_log SET actor =
|
||||
...` both succeed. The log is a draft, not a record.
|
||||
- Routine `DELETE` as "cleanup" is the cardinal P1 violation: the
|
||||
deletion of an audit record is itself an auditable incident, not a
|
||||
housekeeping task. "We deleted old records to save space" is a P3
|
||||
(Retention is Policy, Not Storage) violation *and* a P1 violation —
|
||||
the retention decision is driven by storage cost, and the
|
||||
mechanism is tampering.
|
||||
- The `UPDATE` that rewrites `svc-deploy` → `admin` destroys
|
||||
attribution (a P7 violation stacked on the P1 violation): the
|
||||
original actor is lost, and the replacement (`admin`) is a shared
|
||||
identity that could be any of ten engineers.
|
||||
- **Fix:** the audit sink is append-only *by construction*, not by
|
||||
policy. Write-once storage (WORM bucket, immutable log stream,
|
||||
hash-chained ledger) enforces immutability at the substrate.
|
||||
Retention is a declared policy with a meta-audit of deletions; a
|
||||
human does not run ad-hoc `DELETE` jobs.
|
||||
|
||||
```python
|
||||
# Fix: write to an append-only sink (illustrative — S3 Object Lock,
|
||||
# WORM bucket, or a hash-chained ledger). The API has no update /
|
||||
# delete path; the storage refuses mutation.
|
||||
def write_event(event, actor, target=None, payload=None, request_id=None):
|
||||
record = {
|
||||
"timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
|
||||
"event": event,
|
||||
"actor": actor, # the authenticated principal, not "admin"
|
||||
"target": target,
|
||||
"payload": redact(payload), # see Breach 2 fix
|
||||
"request_id": request_id,
|
||||
"prev_hash": last_hash(), # hash-chaining: tampering breaks the chain
|
||||
}
|
||||
record["hash"] = sha256(canonical_json(record))
|
||||
append_only_sink.write(record) # WORM storage; no update/delete API exists
|
||||
|
||||
# Fix: retention is a declared, reviewed policy — not an ad-hoc DELETE.
|
||||
# When an audit segment ages out, the deletion is itself meta-audited
|
||||
# in a higher-tier log with the rule that authorized it.
|
||||
# (See domains/compliance/data-retention.md and audit-logs.md.)
|
||||
```
|
||||
- See `domains/compliance/audit-logs.md` (Audit Logs are Append-Only)
|
||||
and `domains/compliance/first-principles.md` P1.
|
||||
|
||||
### Breach 2 — Secret Leaked in Audit Log (Compliance P9 Secrets and Sensitive Data are Redacted in Audit)
|
||||
- `payload={"value": value}` writes the raw DB password into the
|
||||
audit record. The append-only log is now a secret store: anyone
|
||||
with `SELECT` on `audit_log` can read production credentials. The
|
||||
log is harder to secure than the secrets manager it read from.
|
||||
- Once the secret is in an append-only log, the remediation is
|
||||
expensive — rotate the secret *and* rewrite the log's access scope
|
||||
(you cannot edit the record; it is append-only). Redaction must
|
||||
happen *at the logging boundary, before the record is written*, not
|
||||
by opportunistic scrubbing after the fact.
|
||||
- The redaction policy here is "nothing" — there is no rule for
|
||||
which fields are redacted, by what mechanism, in which event type.
|
||||
A redaction rule that lives in no one's head and no code is a P9
|
||||
violation waiting to happen (and it happened).
|
||||
- **Fix:** redaction is structural, applied at the logging boundary
|
||||
before the record reaches the append-only sink. The policy is
|
||||
itself auditable (which fields, by what rule, in which event).
|
||||
|
||||
```python
|
||||
# Fix: redaction at the boundary. Log the FACT of the action
|
||||
# (a secret was read), never the CONTENT of the secret.
|
||||
REDACTED_FIELDS = {"value", "token", "password", "authorization", "secret"}
|
||||
|
||||
def redact(payload):
|
||||
if not isinstance(payload, dict):
|
||||
return "[REDACTED:non-object]"
|
||||
out = {}
|
||||
for k, v in payload.items():
|
||||
if k.lower() in REDACTED_FIELDS or "secret" in k.lower():
|
||||
out[k] = "[REDACTED:secret]"
|
||||
else:
|
||||
out[k] = v
|
||||
out["_redaction"] = "secret-value-policy/v1" # the rule is auditable
|
||||
return out
|
||||
|
||||
# The fixed audit record:
|
||||
# {
|
||||
# "event": "config.read",
|
||||
# "actor": "api-server", # the authenticated principal
|
||||
# "target": {"kind": "secret", "id": "db-password"},
|
||||
# "payload": {"value": "[REDACTED:secret]"},
|
||||
# "_redaction": "secret-value-policy/v1",
|
||||
# "request_id": "req_91c2"
|
||||
# }
|
||||
# The fact of the read is logged; the secret never enters the log.
|
||||
```
|
||||
- See `domains/compliance/audit-logs.md` (Redaction at the Boundary)
|
||||
and `domains/compliance/first-principles.md` P9. Cross
|
||||
`domains/security/secrets.md` — the audit-side redaction is the
|
||||
complement of secret management.
|
||||
|
||||
## The Cascade (Two Breaches Compound)
|
||||
|
||||
The two violations compound destructively. The secret enters the
|
||||
mutable log (P9 breach), where it sits readable by any `SELECT`-holder
|
||||
for a week. Then the `cleanup` job `DELETE`s the record (P1 breach) —
|
||||
destroying the evidence that the secret was ever logged, while the
|
||||
secret itself has already been exposed to every reader of the table.
|
||||
The `UPDATE` that rewrites `svc-deploy` → `admin` (a P7 attribution
|
||||
breach stacked on the P1 breach) means that even if a copy of the
|
||||
record survived, the actor who triggered the secret read is no longer
|
||||
identifiable. The team cannot answer "who read the DB password and
|
||||
when" — the log that would answer it was mutated, and the secret it
|
||||
leaked is now in the wild. This is the worst-case interaction of P1
|
||||
and P9: a secret leak with no attributable actor and no surviving
|
||||
record.
|
||||
|
||||
## Cross-Domain Links
|
||||
|
||||
- `domains/compliance/audit-logs.md` — the append-only guarantee and
|
||||
the redaction-at-boundary rule this code violates.
|
||||
- `domains/compliance/first-principles.md` — P1 (Append-Only) and P9
|
||||
(Redacted) are the two breached principles; P7 (Attributable) is
|
||||
breached by the `UPDATE` rewrite.
|
||||
- `domains/compliance/evidence.md` — an audit log that can be
|
||||
`DELETE`d is not admissible evidence; the append-only guarantee is
|
||||
what makes it admissible.
|
||||
- `domains/compliance/data-retention.md` — retention is a declared
|
||||
policy with meta-audited deletions, not an ad-hoc `DELETE` job.
|
||||
- `domains/security/secrets.md` — redaction at the logging boundary
|
||||
is the audit-side complement of secret management.
|
||||
- `domains/observability/logging.md` — audit logs are structured
|
||||
logging with an append-only guarantee; the logging primitives
|
||||
compose here.
|
||||
@@ -0,0 +1,175 @@
|
||||
# Bad Example: i18n String Concatenation
|
||||
|
||||
> A checkout component that violates Atelier's i18n principles. Each
|
||||
> violation is cited, then fixed.
|
||||
|
||||
## The Code
|
||||
|
||||
```typescript
|
||||
// Checkout.tsx — the cardinal i18n violation
|
||||
function CartSummary({ itemCount, name, total, currency, date }) {
|
||||
// P3 VIOLATION: inline string concatenation. The source-language
|
||||
// word order ("Welcome, {name}! You have {n} items") is baked into
|
||||
// code. Every locale with different word order is broken.
|
||||
const welcome = "Welcome, " + name + "!";
|
||||
|
||||
// P4 VIOLATION: hand-rolled plural branching. `if (n === 1)` encodes
|
||||
// exactly English's one/other rule. Arabic (six categories), Russian
|
||||
// (three), Polish (three) are all wrong.
|
||||
const items =
|
||||
itemCount === 1 ? "1 item" : itemCount + " items";
|
||||
|
||||
// P5 VIOLATION: hand-rolled currency + date formatting. "$" + total
|
||||
// is wrong for de-DE (symbol, grouping, placement). The date
|
||||
// `getMonth() + 1 + "/" + getDay()` is US-only (mm/dd/yyyy).
|
||||
const price = "$" + total.toFixed(2);
|
||||
const d = new Date(date);
|
||||
const dateStr = (d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>{welcome}</h1>
|
||||
<p>{items} · {price} · {dateStr}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
```typescript
|
||||
// The "resource" file — a custom CSV the team hand-rolled.
|
||||
// locale,en_us
|
||||
// welcome_prefix,Welcome,
|
||||
// item_singular,item
|
||||
// item_plural,items
|
||||
//
|
||||
// This is a P3 violation on its own: a custom format no standard
|
||||
// tool (xgettext, i18next, FormatJS) can extract from or compose with.
|
||||
```
|
||||
|
||||
The team runs their first i18n test against real Arabic translations —
|
||||
after the string freeze, after the translator was paid. The Arabic
|
||||
build renders `"Welcome, محمد!"` with the name on the wrong side of
|
||||
the comma, `"1 items"` for a single item (Arabic has six plural
|
||||
categories, not two), and the price as `"$1,234.56"` (Arabic-Egypt
|
||||
formats as `"١٬٢٣٤٫٥٦ ج.م."`). Every screen is a rewrite, not a patch.
|
||||
|
||||
## What Makes It Bad
|
||||
|
||||
### Inline String Concatenation (i18n P3 Resources are External, Not Inline)
|
||||
- `"Welcome, " + name + "!"` bakes English word order into code. In
|
||||
Japanese the name comes first (`ようこそ、محمدさん!`); in Arabic the
|
||||
structure differs again. The concatenation is invisible to the
|
||||
extraction pipeline (`xgettext`, `i18next-parser`) — the translator
|
||||
never sees it as a unit, and the string cannot be versioned or
|
||||
rolled back as a whole.
|
||||
- The custom `.csv` "resource" store is a second P3 violation: no
|
||||
standard tool reads it, it carries no plural grammar, and it cannot
|
||||
compose with the ICU formatting layer.
|
||||
- **Fix:** strings live in a standard locale resource file, addressed
|
||||
by key. Code calls `t("welcome", { name })`; the resource carries
|
||||
the parameterized message.
|
||||
|
||||
```json
|
||||
// en-US.json (ICU MessageFormat)
|
||||
{
|
||||
"checkout.welcome": "Welcome, {name}!",
|
||||
"checkout.cart.summary": "{count, plural, one {# item} other {# items}} · {price} · {date}"
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
// ar-EG.json — six plural categories per CLDR; the code is identical
|
||||
{
|
||||
"checkout.welcome": "أهلاً بك، {name}!",
|
||||
"checkout.cart.summary": "{count, plural, zero {لا عناصر} one {عنصر واحد} two {عنصران} few {# عناصر} many {# عنصرًا} other {# عنصر}} · {price} · {date}"
|
||||
}
|
||||
```
|
||||
- See `domains/i18n/locale-resources.md` (Resources are the Boundary)
|
||||
and `domains/i18n/first-principles.md` P3.
|
||||
|
||||
### Hand-Rolled Plural Branching (i18n P4 Plural and Gender are Parameterized)
|
||||
- `itemCount === 1 ? "1 item" : itemCount + " items"` encodes
|
||||
English's one/other rule and nothing else. Arabic has six
|
||||
categories (zero, one, two, few, many, other); Russian has three
|
||||
(one, few, many); Polish has three with different boundaries. A
|
||||
two-branch `if` is a C1 (Correctness) violation masquerading as a
|
||||
shortcut — it returns a wrong answer for every non-English locale.
|
||||
- **Fix:** the count goes to ICU MessageFormat; the formatter
|
||||
consults `Intl.PluralRules` for the active locale; the resource
|
||||
carries the variant for that category. The code passes the count,
|
||||
nothing more.
|
||||
|
||||
```typescript
|
||||
// The code passes the count; the resource + formatter pick the form.
|
||||
t("checkout.cart.summary", { count: itemCount, price, date });
|
||||
// Intl.PluralRules("ar-EG").select(1) === "one" -> "عنصر واحد"
|
||||
// Intl.PluralRules("ar-EG").select(2) === "two" -> "عنصران"
|
||||
// Intl.PluralRules("ar-EG").select(5) === "few" -> "٥ عناصر"
|
||||
```
|
||||
- See `domains/i18n/locale-resources.md` (Plural and Gender in
|
||||
Resources) and `domains/i18n/formatting.md` (Plural Rules).
|
||||
|
||||
### Hand-Rolled Currency and Date Formatting (i18n P5 Formatting is Locale-Aware)
|
||||
- `"$" + total.toFixed(2)` hardcodes the US dollar symbol, US
|
||||
grouping (`,`), and US placement (symbol before the number). In
|
||||
`de-DE` the euro formats as `"1.234,56 €"` (symbol after, dot
|
||||
grouping). In `ar-EG` the pound formats as `"١٬٢٣٤٫٥٦ ج.م."`
|
||||
(Arabic-Indic digits, different grouping).
|
||||
- `(d.getMonth() + 1) + "/" + d.getDate() + "/" + d.getFullYear()`
|
||||
produces `11/7/2024` — US `mm/dd/yyyy`. Most of the world reads
|
||||
`dd/mm/yyyy`; ISO is `yyyy-mm-dd`. A hand-rolled date formatter
|
||||
encodes one locale's convention and silently produces wrong output
|
||||
for every other.
|
||||
- **Fix:** `Intl.NumberFormat` and `Intl.DateTimeFormat` with a BCP
|
||||
47 locale tag. CLDR is the source of truth; `Intl` is the runtime.
|
||||
|
||||
```typescript
|
||||
new Intl.NumberFormat("ar-EG", { style: "currency", currency: "EGP" })
|
||||
.format(1234.56); // "١٬٢٣٤٫٥٦ ج.م."
|
||||
new Intl.DateTimeFormat("ar-EG", { dateStyle: "medium" })
|
||||
.format(new Date(date)); // "٧ نوفمبر ٢٠٢٤"
|
||||
```
|
||||
- See `domains/i18n/formatting.md` (the Intl surface, dates, numbers,
|
||||
currencies) and `domains/i18n/first-principles.md` P5.
|
||||
|
||||
### Source Language Treated as the Default (i18n P1 Source Language is a Locale)
|
||||
- The component has no resource layer at all for the source locale —
|
||||
English is "just the strings in the code." When the first second
|
||||
locale arrives, the fix is a rewrite (extract every string,
|
||||
restructure every concatenation), not a patch. The source language
|
||||
is `en-US`, a locale among many — it is not `null`.
|
||||
- **Fix:** extract source strings into `en-US.json` from day one,
|
||||
even before a second locale exists. The resource layer is the
|
||||
boundary from the first commit.
|
||||
- See `domains/i18n/first-principles.md` P1 and
|
||||
`domains/uiux/copywriting.md`.
|
||||
|
||||
## The Cascade
|
||||
|
||||
The violations compound. Inline concatenation makes strings invisible
|
||||
to the extraction pipeline, so the translator never receives them as
|
||||
units — they reconstruct them by reading the code. Hand-rolled
|
||||
plurals return wrong answers for every non-English locale, so the
|
||||
Arabic build ships `"1 items"` for a single item. Hand-rolled
|
||||
formatting produces US-shaped output everywhere, so the price and
|
||||
date are wrong for `de-DE`, `ar-EG`, `zh-Hans-CN`, and every other
|
||||
locale. And because the first i18n test ran against real translations
|
||||
(a P8 violation — pseudo-locales should have surfaced all of this
|
||||
while the fix was still cheap), the defects are found after the
|
||||
string freeze, after the translator was paid, and after the release
|
||||
date was promised. The fix is now a re-translation and a re-release,
|
||||
not a commit.
|
||||
|
||||
## Cross-Domain Links
|
||||
|
||||
- `domains/i18n/locale-resources.md` — the resource layer this code
|
||||
lacks; the standard formats (`.po`, JSON, Fluent, ICU Resource
|
||||
Bundle) it should have used.
|
||||
- `domains/i18n/formatting.md` — the `Intl`/ICU/CLDR formatting this
|
||||
code should call instead of hand-rolling.
|
||||
- `domains/i18n/first-principles.md` — P3, P4, P5, and P8 (pseudo-
|
||||
locales test early).
|
||||
- `domains/uiux/copywriting.md` — copy lives in resources, not in
|
||||
code.
|
||||
- `domains/api/error-responses.md` — the same parameterized-message
|
||||
discipline applies to localized API errors.
|
||||
@@ -0,0 +1,226 @@
|
||||
# Good Example: AI/ML Reproducible Training Run
|
||||
|
||||
> A training run that follows Atelier's AI/ML principles. Each aspect
|
||||
> cites the principle it satisfies. Scope per D-023: this is
|
||||
> engineering discipline (reproducibility, versioning, lineage,
|
||||
> serving), **not** algorithm or model design — no architecture
|
||||
> choice, hyperparameter tuning, or model-family comparison appears
|
||||
> here.
|
||||
|
||||
## The Run
|
||||
|
||||
A training run `2026-08-05T09:12:00Z#run-42` produces model
|
||||
`registry/payments-fraud@sha256:b5e1...aa0`. Every input that shaped
|
||||
the model is pinned, named, and recoverable; the eval was declared
|
||||
before training; the model is an addressed artifact in a registry;
|
||||
the rollback path names the prior model and the prior dataset.
|
||||
|
||||
### The Reproducibility Contract
|
||||
|
||||
```yaml
|
||||
# lineage/run-42.yaml — the lineage root, committed alongside the code
|
||||
run_id: 2026-08-05T09:12:00Z#run-42
|
||||
dataset: s3://ml-data/train@sha256:7f3a...e21
|
||||
splits: dvc.yaml@commit a1b2c4d
|
||||
code: git@a1b2c4d
|
||||
config: configs/train.yaml@commit a1b2c4d
|
||||
environment: ghcr.io/org/train-img@sha256:9c2d...f88
|
||||
eval_spec: configs/eval.yaml@commit a1b2c4d
|
||||
model_digest: registry/payments-fraud@sha256:b5e1...aa0
|
||||
status: passed # eval gate passed -> eligible for promotion
|
||||
```
|
||||
|
||||
- Lose any line and the run is anecdote, not evidence. The record is
|
||||
the lineage root: a prediction cites the `model_digest`, which
|
||||
cites this `run_id`, which cites everything above.
|
||||
|
||||
### Data is Versioned (DVC, content-hashed)
|
||||
|
||||
```ini
|
||||
# dvc.yaml — the split config is versioned in git, the data in the
|
||||
# content-addressed object store. Both are pinned by commit + hash.
|
||||
stages:
|
||||
prepare:
|
||||
cmd: python src/prepare.py --input data/raw --out data/splits
|
||||
deps:
|
||||
- data/raw
|
||||
- src/prepare.py
|
||||
outs:
|
||||
- data/splits/train.parquet
|
||||
- data/splits/val.parquet
|
||||
- data/splits/test.parquet
|
||||
# The dataset hash (sha256:7f3a...e21) is recorded in the lineage
|
||||
# contract above. "s3://ml-data/latest" would be a P2 violation.
|
||||
```
|
||||
|
||||
```bash
|
||||
# The dataset is pinned by content hash, not by a mutable path.
|
||||
$ dvc get s3://ml-data/train --rev sha256:7f3a...e21
|
||||
# The split is a deterministic function of (dataset version, split
|
||||
# config, random seed). Two runs on the same pinned inputs produce
|
||||
# the same splits.
|
||||
```
|
||||
|
||||
### Code and Config are Versioned (git)
|
||||
|
||||
```yaml
|
||||
# configs/train.yaml@commit a1b2c4d — versioned with the code
|
||||
# (No algorithm/hyperparameter content is illustrated here — this is
|
||||
# the engineering discipline of pinning the config, not the model
|
||||
# design inside it. Per D-023, algorithm choice is out of scope.)
|
||||
seed: 42
|
||||
splits:
|
||||
train: data/splits/train.parquet
|
||||
val: data/splits/val.parquet
|
||||
test: data/splits/test.parquet # held out, never touched by training
|
||||
```
|
||||
|
||||
### Environment is Pinned (container digest)
|
||||
|
||||
```dockerfile
|
||||
# The training environment is an image addressed by digest, not :latest.
|
||||
# ghcr.io/org/train-img@sha256:9c2d...f88
|
||||
FROM python:3.11-slim
|
||||
# dependencies pinned in requirements.txt with hashes
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
```
|
||||
|
||||
```text
|
||||
# requirements.txt — pinned + hash-pinned (pip-compile / pip-audit)
|
||||
dvc==3.50.2 \
|
||||
--hash=sha256:1c8a...e7
|
||||
mlflow==2.16.0 \
|
||||
--hash=sha256:9b2f...a1
|
||||
# No unpinned ranges. A rerun pulls the exact same wheels.
|
||||
```
|
||||
|
||||
### Evaluation is Defined Before Training (P4)
|
||||
|
||||
```yaml
|
||||
# configs/eval.yaml@commit a1b2c4d — committed BEFORE training runs.
|
||||
# The metrics, splits, and pass/fail thresholds are a-priori; they
|
||||
# are the contract the model must satisfy to leave the experiment.
|
||||
metrics:
|
||||
- name: precision_at_threshold
|
||||
threshold: ">= 0.92"
|
||||
- name: recall_at_threshold
|
||||
threshold: ">= 0.85"
|
||||
- name: false_positive_rate
|
||||
threshold: "<= 0.03"
|
||||
split: data/splits/test.parquet # held out, never in training
|
||||
gate: all_metrics_pass # AND of all thresholds; no cherry-pick
|
||||
# The eval schema equals the serving input contract (serving.md P8):
|
||||
# feature names, types, ranges match the production boundary exactly.
|
||||
```
|
||||
|
||||
- Metrics chosen after seeing scores would be a P4 violation: the eval
|
||||
would be rationalizing, not measuring. See
|
||||
`domains/ai-ml/model-evaluation.md`.
|
||||
|
||||
### The Model is a Versioned Artifact (MLflow registry)
|
||||
|
||||
```bash
|
||||
# After the eval gate passes, the model is registered as an immutable
|
||||
# artifact addressed by digest, then promoted by stage.
|
||||
$ mlflow models register \
|
||||
--name payments-fraud \
|
||||
--model-uri runs:/run-42/model \
|
||||
--description "run-42, dataset sha256:7f3a...e21, eval passed"
|
||||
# registry/payments-fraud@sha256:b5e1...aa0
|
||||
# Stages: None -> Staging -> Production. Promotion is a registry
|
||||
# operation, not a file copy. Never "latest".
|
||||
```
|
||||
|
||||
### The Pipeline Composes (P9)
|
||||
|
||||
```text
|
||||
# The training flow is a pipeline with explicit stages and contracts,
|
||||
# not a notebook. Each stage has named inputs and named outputs.
|
||||
prepare(dataset@hash) -> split(dvc.yaml) -> train(config, env@digest)
|
||||
-> eval(eval.yaml, test@hash) -> [gate: pass] -> register(model@digest)
|
||||
|
|
||||
+-> [gate: fail] -> abort, no promote
|
||||
# A notebook in this path would be a P9 violation: implicit state,
|
||||
# human-dependent order, unreproducible.
|
||||
```
|
||||
|
||||
## What Makes It Good
|
||||
|
||||
### Reproducibility is First Class (AI/ML P1, C1, C5)
|
||||
- data + code + config + environment are all pinned. A second
|
||||
engineer on a second laptop checks out commit `a1b2c4d`, pulls the
|
||||
dataset by hash, pulls the image by digest, and reproduces the run
|
||||
bit-for-bit. The run is reviewable because it is recreatable.
|
||||
- See `domains/ai-ml/first-principles.md` P1 and
|
||||
`domains/devops/first-principles.md` P1 Reproducibility.
|
||||
|
||||
### Data is Versioned, Not Just Code (AI/ML P2, C5, C7)
|
||||
- The dataset is `s3://ml-data/train@sha256:7f3a...e21`, not
|
||||
`s3://ml-data/latest`. A model trained on "the data" is a model
|
||||
trained on an unknown input — a C1 violation. DVC pins the data the
|
||||
way git pins the code.
|
||||
- See `domains/ai-ml/data-versioning.md` (dataset hashing, the DVC /
|
||||
Delta Lake / LakeFS comparison) and `domains/data/migrations.md`.
|
||||
|
||||
### Lineage is Traceable End-to-End (AI/ML P3, C7, C1)
|
||||
- prediction → model → run-42 → dataset → source. Every edge is
|
||||
named; no orphan model. A serving regression traces back to the
|
||||
exact dataset and code that built the model, which is how drift is
|
||||
diagnosed (data drift vs concept drift vs prediction drift).
|
||||
- See `domains/ai-ml/data-versioning.md` (lineage record) and
|
||||
`domains/observability/logging.md`.
|
||||
|
||||
### Evaluation Defined Before Training (AI/ML P4, C1, C2)
|
||||
- `eval.yaml` was committed before `train` ran. The gate is
|
||||
`all_metrics_pass`; a failing metric aborts promotion. Cherry-
|
||||
picking a metric post-hoc is a correctness violation — the eval
|
||||
would no longer measure the model.
|
||||
- See `domains/ai-ml/model-evaluation.md` (eval-as-a-gate) and
|
||||
`domains/testing/first-principles.md` (tests as specification).
|
||||
|
||||
### Models are Versioned Artifacts (AI/ML P5, C5, C6)
|
||||
- The model is `registry/payments-fraud@sha256:b5e1...aa0`, promoted
|
||||
Staging → Production. A serving endpoint that pulled `latest` would
|
||||
be serving an unknown model with no rollback. The registry is to
|
||||
models what a container registry is to images.
|
||||
- See `domains/ai-ml/serving.md` (the model is an addressed artifact)
|
||||
and `domains/devops/first-principles.md` P7 Immutability.
|
||||
|
||||
### Rollback Includes the Model (AI/ML P10, C5)
|
||||
- If production regresses, the rollback restores the prior model
|
||||
digest `registry/payments-fraud@sha256:a1c4...f09` AND the prior
|
||||
serving code. A rollback that redeploys old code but keeps the new
|
||||
model has not rolled back — the model was the thing that regressed.
|
||||
- See `domains/ai-ml/serving.md` (Rollback Includes the Model) and
|
||||
`domains/devops/first-principles.md` P4 Rollback First.
|
||||
|
||||
## What This Example Does NOT Do (And Why That's Good)
|
||||
|
||||
- Does **not** reference the dataset by a mutable path —
|
||||
`s3://ml-data/latest` would be a P2 violation.
|
||||
- Does **not** choose metrics after seeing scores — that is a P4
|
||||
violation (rationalizing, not measuring).
|
||||
- Does **not** pull `latest` from the model registry — that is a P5
|
||||
violation (unknown model, no rollback).
|
||||
- Does **not** contain algorithm/architecture/hyperparameter content
|
||||
— per D-023, those are research choices, not engineering
|
||||
principles, and have no derivation in the core C-rules.
|
||||
- Does **not** run from a notebook — a notebook in the pipeline path
|
||||
is a P9 violation (implicit state, unreproducible).
|
||||
|
||||
## Cross-Domain Links
|
||||
|
||||
- `domains/ai-ml/data-versioning.md` — the DVC pinning, the lineage
|
||||
record, the tool comparison (DVC / Delta Lake / LakeFS).
|
||||
- `domains/ai-ml/serving.md` — the model is promoted as an addressed
|
||||
artifact; the serving boundary validates inputs against the same
|
||||
schema as the eval.
|
||||
- `domains/ai-ml/model-evaluation.md` — the eval-as-a-gate that this
|
||||
run must pass before promotion.
|
||||
- `domains/devops/first-principles.md` P1 Reproducibility — the
|
||||
non-negotiable this run inherits.
|
||||
- `domains/data/migrations.md` — data versioning parallels schema
|
||||
migration discipline.
|
||||
- `domains/observability/logging.md` — the lineage record is a
|
||||
structured, append-only log of provenance.
|
||||
@@ -0,0 +1,197 @@
|
||||
# Good Example: GitOps Pull Request
|
||||
|
||||
> A pull request that changes ArgoCD Application manifests following
|
||||
> Atelier's GitOps + Operators principles. Each aspect cites the
|
||||
> principle it satisfies.
|
||||
|
||||
## The PR
|
||||
|
||||
A PR titled `promote payments-api 1.2.3 to prod` opened against the
|
||||
GitOps repo `platform/gitops`. It changes the `targetRevision` of the
|
||||
payments-api Application from `1.2.2` to `1.2.3`, adds a sync-wave
|
||||
annotation to a new migration Job, and tightens the AppProject's
|
||||
destination allow-list. CI runs plan/diff; nothing pushes to the
|
||||
cluster.
|
||||
|
||||
### The Commit
|
||||
|
||||
```yaml
|
||||
# manifests/prod/payments-api.yaml — the only file changed
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: payments-api
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
source:
|
||||
repoURL: https://git.example.com/platform/payments
|
||||
targetRevision: 1.2.3 # was 1.2.2 — pinned, not latest
|
||||
path: manifests/prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: payments
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=false
|
||||
- PrunePropagationPolicy=foreground
|
||||
```
|
||||
|
||||
```yaml
|
||||
# manifests/prod/payments-db-migration.yaml — new file, wave-ordered
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: payments-db-migrate-1.2.3
|
||||
namespace: payments
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1" # PreSync: run before the app
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
ttlSecondsAfterFinished: 86400
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
serviceAccountName: payments-migrator
|
||||
containers:
|
||||
- name: migrate
|
||||
image: registry.example.com/payments/migrate:1.2.3
|
||||
args: ["up", "--to", "1.2.3"]
|
||||
```
|
||||
|
||||
```yaml
|
||||
# projects/payments.yaml — AppProject tightened in the same PR
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: AppProject
|
||||
metadata:
|
||||
name: payments
|
||||
namespace: argocd
|
||||
spec:
|
||||
sourceRepos:
|
||||
- https://git.example.com/platform/payments
|
||||
destinations:
|
||||
- server: https://kubernetes.default.svc
|
||||
namespace: payments # prod-app can only deploy to payments ns
|
||||
clusterResourceWhitelist:
|
||||
- group: ""
|
||||
kind: Namespace # allowed to create its own namespace
|
||||
roles:
|
||||
- name: payments-team
|
||||
policies:
|
||||
- p, proj:payments:payments-team, applications, sync, payments/*, allow
|
||||
```
|
||||
|
||||
### The CI Pipeline (runs on the PR, before merge)
|
||||
|
||||
```text
|
||||
# .github/workflows/gitops-plan.yml (illustrative steps)
|
||||
- name: validate manifests
|
||||
run: argocd app manifests manifests/prod/ | kubeconform -strict
|
||||
|
||||
- name: diff against live cluster (read-only, no apply)
|
||||
run: argocd app diff payments-api --server $ARGOCD_SERVER --auth-token $READ_ONLY_TOKEN
|
||||
# CI holds a READ-ONLY ArgoCD token. It never holds kubectl rights.
|
||||
# A non-empty diff is the PR's proposed change, rendered for review.
|
||||
|
||||
- name: opa gate (admission policy pre-check)
|
||||
run: opa eval -i manifests/prod/ -d policies/ "data.k8s.admission.deny"
|
||||
# Policy violations fail the PR before merge, not after deploy.
|
||||
```
|
||||
|
||||
## What Makes It Good
|
||||
|
||||
### Git is the Source of Truth (GitOps P1, C1 Correctness)
|
||||
- The promotion is a commit. The cluster's desired state is a
|
||||
derivative of this repo; the repo is the authority. If the change is
|
||||
wrong, `git revert` is the rollback — the recovery path is the
|
||||
history.
|
||||
- See `domains/gitops-operators/first-principles.md` P1 and
|
||||
`domains/gitops-operators/argocd.md` (Application CRD).
|
||||
|
||||
### Pull, Don't Push (GitOps P3, C4 Locality)
|
||||
- CI holds a **read-only** ArgoCD token for `app diff`. It holds no
|
||||
`kubectl` rights against the production cluster. The cluster's
|
||||
ArgoCD controller pulls the merged commit; nothing pushes to the
|
||||
cluster. A compromised CI token can read, not deploy.
|
||||
- See `domains/gitops-operators/argocd.md` (RBAC and SSO) and
|
||||
`domains/gitops-operators/flux.md` for the same pull boundary from
|
||||
the Flux side.
|
||||
|
||||
### State is Immutable and Versioned (GitOps P5, C5 Reversibility)
|
||||
- `targetRevision: 1.2.3` — the Application pins a specific chart
|
||||
revision, not `latest`. The commit that changed it is a permanent
|
||||
record; `git revert` restores `1.2.2` and ArgoCD's `selfHeal`
|
||||
converges the cluster back. No force-push; history is the audit
|
||||
trail.
|
||||
- See `domains/gitops-operators/first-principles.md` P5 and
|
||||
`domains/infrastructure-as-code/state.md` (State is Truth).
|
||||
|
||||
### Sync Waves Order Correctness (GitOps P4, C1)
|
||||
- The migration Job carries `argocd.argoproj.io/sync-wave: "-1"` so
|
||||
it runs in `PreSync` before the payments-api Deployment that
|
||||
depends on the new schema. Wave ordering is a correctness
|
||||
mechanism, not performance — the app starting before its migration
|
||||
is a correctness bug.
|
||||
- See `domains/gitops-operators/argocd.md` (Sync Waves and Hooks).
|
||||
|
||||
### Reconcile, Don't Mutate by Hand (GitOps P8)
|
||||
- `selfHeal: true` + `prune: true` means a hand-edited drift on a
|
||||
managed resource is overwritten on the next loop. The fix for drift
|
||||
is a new commit, not `kubectl edit`. The PR author does not SSH into
|
||||
the cluster to "fix" anything.
|
||||
- See `domains/gitops-operators/argocd.md` (Diff and Drift) and
|
||||
`domains/gitops-operators/first-principles.md` P8.
|
||||
|
||||
### Least Privilege Reconciliation (GitOps P10, C8 Economy)
|
||||
- The AppProject `payments` restricts the Application to the
|
||||
`payments` namespace and the `payments` repo. The controller's
|
||||
ServiceAccount (not shown) is bound to a namespace-scoped Role, not
|
||||
`cluster-admin`. The PR *tightens* the allow-list — least privilege
|
||||
is a direction, not a one-time setting.
|
||||
- See `domains/gitops-operators/argocd.md` (RBAC and SSO) and
|
||||
`domains/kubernetes/rbac.md`.
|
||||
|
||||
### Policy is a Gate (Compliance P5, cross-link)
|
||||
- The `opa eval` step runs the admission policy against the proposed
|
||||
manifests before merge. A violation fails the PR; the non-compliant
|
||||
state is never realized. Detection is not enforcement; this is
|
||||
enforcement.
|
||||
- See `domains/compliance/policy-as-code.md` and
|
||||
`domains/devops/ci-cd.md`.
|
||||
|
||||
### Failure is Observable (GitOps P9)
|
||||
- A sync failure or health degradation on `payments-api` emits
|
||||
ArgoCD status (`Degraded` / `OutOfSync`) and a notification. Silent
|
||||
drift is the bug; this PR does not disable notifications.
|
||||
- See `domains/gitops-operators/argocd.md` (Health and Status) and
|
||||
`domains/observability/metrics.md`.
|
||||
|
||||
## What This PR Does NOT Do (And Why That's Good)
|
||||
|
||||
- Does **not** run `kubectl apply` from CI — that is the push pattern,
|
||||
a P3 violation (see `examples/bad/` for the anti-pattern).
|
||||
- Does **not** use `argocd app set` as the steady state — the change
|
||||
is in git, not in an imperative command's history.
|
||||
- Does **not** store raw Secrets in the GitOps repo — secrets arrive
|
||||
via Sealed Secrets / SOPS / External Secrets, encrypted in git.
|
||||
- Does **not** float `targetRevision: latest` — the Application pins
|
||||
a version; "latest" is an unknown model of the system.
|
||||
|
||||
## Cross-Domain Links
|
||||
|
||||
- `domains/gitops-operators/argocd.md` — the Application CRD, sync
|
||||
waves, RBAC/AppProjects, and the pull model.
|
||||
- `domains/gitops-operators/flux.md` — the same PR pattern from the
|
||||
Flux side (Kustomization CRD, per-cluster autonomy).
|
||||
- `domains/kubernetes/workloads.md` — the Deployment/Job the
|
||||
Application reconciles.
|
||||
- `domains/kubernetes/rbac.md` — the ServiceAccount + Role the
|
||||
controller and the migration Job run as.
|
||||
- `domains/compliance/policy-as-code.md` — the OPA gate is a
|
||||
compliance-as-a-gate enforcement point.
|
||||
- `domains/devops/P4 Rollback First` — `git revert` is the rollback;
|
||||
`selfHeal` is the convergence.
|
||||
Reference in New Issue
Block a user