diff --git a/.mcp.json b/.mcp.json deleted file mode 100644 index 6efc1ca..0000000 --- a/.mcp.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "mcpServers": { - "gitea": { - "type": "http", - "url": "https://git-mcp.farh.net/mcp", - "headers": { - "Authorization": "Bearer ${GITEA_TOKEN}" - } - } - } -} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c9ae143 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,54 @@ +# AGENTS.md + +This repository (`groombook/api`) is part of the GroomBook application stack. The +authoritative process, quality bar, and safety rules live in the shared +[`groombook/org`](https://git.farh.net/groombook/org) skills repository. Read +those first; this file is only a pointer. + +## Authoritative skills + +- **SDLC (branching, PRs, phases, handoffs):** + [`groombook/org/skills/sdlc/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/sdlc/SKILL.md) +- **Coding standards (priority ordering, PR discipline, tests, no-hardcoded-values, CalVer):** + [`groombook/org/skills/coding-standards/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/coding-standards/SKILL.md) +- **Safety (no plaintext secrets, no direct `kubectl apply` to `groombook`, no self-merge, board approval for destructive actions):** + [`groombook/org/skills/safety/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/safety/SKILL.md) + +For human contributors and humans reviewing agent work, see +[`CONTRIBUTING.md`](./CONTRIBUTING.md) in this repo for the phase-by-phase PR +flow and the `uat→main` merge-gate policy summary. + +## Non-negotiable operational rules + +These mirror the org skills; they are restated here so any agent landing in +this repo sees them without a cross-repo fetch. + +- **All changes go through a PR.** Never push directly to `dev`, `uat`, or `main`. +- **Branch strategy:** `feature/` → `dev` → `uat` → `main`. Engineers + always target `dev` first. +- **No self-merge contract.** The engineer who opened a PR clicks merge only + after the named reviewer (CI / QA / UAT / Security / CTO per phase) + approves. Issue-thread QA / UAT / security approvals do **not** clear the + Gitea `required_approvals` gate on `uat→main` — only a Gitea **Approve** + click from a member of the `approvals_whitelist_username` does. On this + repo that whitelist is `["gb_flea", "gb_dogfather"]` (engineer team). + Board-level accounts cannot give the Approve click by policy. +- **Always include `cc @cpfarhood`** at the bottom of every PR body for + board visibility (not as a reviewer). +- **Secrets in code are forbidden.** Use Bitnami Sealed Secrets; never commit + plaintext. See the `safety` skill. +- **Production (`groombook` namespace) is Flux-managed.** Never + `kubectl apply` directly. Infrastructure changes go through PRs in + `groombook/infra`. + +## Local development + +See the repo's own README, package scripts, and CI workflow. The +authoritative pipeline (Gitea Actions, image build, deploy hooks) is the +shared `groombook/infra` overlay; do not reimplement it here. + +## When uncertain + +If a task conflicts with the org skills, **the org skills win**. Open an +issue in `groombook/org` to propose a change rather than encoding a local +exception. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..64a918a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,117 @@ +# Contributing to `groombook/api` + +Thanks for contributing. This document is the human-facing companion to +[`AGENTS.md`](./AGENTS.md) and the authoritative +[`groombook/org`](https://git.farh.net/groombook/org) skills. The org skills +govern; this file is a quick-reference for the human/agent PR flow in this +repo. + +## Branch strategy + +Three long-lived branches; one PR per promotion step. + +| Branch | Environment | Who merges | Prerequisites for merge | +|---------|-------------|-----------|-------------------------| +| `dev` | Dev | Engineer | CI passes | +| `uat` | UAT | Engineer | QA code review approval | +| `main` | Production | Engineer | UAT validation + CTO Gitea Approve when the `uat→main` merge-gate policy applies (see below) | + +Engineers always target `dev` first. Feature branches: `/`. + +## Phase-by-phase PR flow + +### Phase 1 — Dev + +1. Branch from `dev`: `git checkout -b / origin/dev`. +2. Write code + tests. Run unit tests, type check, and lint locally (or rely on CI). +3. Open a PR against `dev`: + ```bash + tea pr create --base dev --title "..." --body "..." + ``` + Include `cc @cpfarhood` at the bottom of the body for board visibility. +4. CI must pass. CI green → engineer self-merges. +5. CI builds and deploys to Dev automatically. + +### Phase 2 — UAT promotion + +1. Open a PR from `dev` to `uat`. +2. CI must pass. +3. **QA (Lint Roller)** reviews and approves on the Gitea PR. +4. QA approved → engineer self-merges. +5. CI builds and deploys to UAT automatically. + +### Phase 3 — UAT regression + Security review + +1. **UAT (Shedward Scissorhands)** runs full regression against UAT — every + feature, old and new, no exceptions. +2. **Security (Barkley Trimsworth)** reviews the changes. +3. Failures in either gate bounce back to Phase 1. + +### Phase 4 — Production promotion (`uat → main`) + +This is the gate the org PR +[`groombook/org#13`](https://git.farh.net/groombook/org/pulls/13) defines. +The full rule is in +[`groombook/org/skills/sdlc/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/sdlc/SKILL.md) +and +[`groombook/org/skills/coding-standards/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/coding-standards/SKILL.md); +the summary is below. + +**The CTO Gitea Approve click is NOT the default gate.** Once the four +pre-gates (QA, UAT deploy, UAT regression, security) are green, the engineer +self-merges. + +**A CTO Gitea Approve click IS required** only for PRs in one of three +categories: + +1. **Novel auth / session paths** — login, OIDC, OOBE, session middleware, + token issuance, password reset, MFA, new auth provider integrations. + Routine auth-gated UI (button styling, error messages, form layout) is + **not** in this category. +2. **Infra / prod-affecting merges** — deploys, infra manifests, secrets, + GitOps overlays, CI/CD, `main` branch protection, production + routing/ingress, prod state mutations. All Phase 5 infra overlay PRs in + `groombook/infra` require CTO Gitea Approve without exception. +3. **Risk-flagged merges** — `risk:cto-approve` label, or explicit CTO/CEO + sign-off request in the PR or issue thread. + +The engineer opens the `uat→main` PR, classifies it against the three +categories above, and adds `cc @cpfarhood`. If the PR is in scope, the CTO +clicks Approve; once approved (and the four pre-gates are green), the +engineer merges. + +### Phase 5 — Production deployment + +A separate PR in `groombook/infra` bumps the overlay image tag for prod. +Handed to QA (Lint Roller) for review, then self-merged by the engineer. + +## The four pre-gates (uat→main) + +A `uat→main` PR is mergeable when **all four** are green: + +1. **QA code review** — done on the dev→uat promotion PR. +2. **UAT deploy** — the UAT image built from the uat tip is live in UAT. +3. **UAT regression** — Shedward's full-feature UAT pass is green (no + pre-existing defects, no new defects). +4. **Security review** — Barkley's security code review is green. + +Issue-thread QA / UAT / security approvals do **not** clear the Gitea +`required_approvals` gate. Only a Gitea **Approve** click from a member of +the `approvals_whitelist_username` for `main` clears it. In this repo that +whitelist is the engineer team (`gb_flea`, `gb_dogfather`). + +## Style, tests, and quality bar + +See +[`groombook/org/skills/coding-standards/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/coding-standards/SKILL.md) +for the engineering priority ordering, test requirements, no-hardcoded-values +rules, CalVer versioning policy, and the `git.farh.net` container registry +policy. + +## Safety + +See +[`groombook/org/skills/safety/SKILL.md`](https://git.farh.net/groombook/org/src/branch/main/skills/safety/SKILL.md) +for the non-negotiable rules: no plaintext secrets, no `kubectl apply` to +`groombook`, no self-merge, no direct `tofu` runs, board approval for +destructive actions, escalation protocol. diff --git a/packages/db/src/reset.ts b/packages/db/src/reset.ts index fb88e20..9bb6d8f 100644 --- a/packages/db/src/reset.ts +++ b/packages/db/src/reset.ts @@ -1,145 +1 @@ -/** - * reset.ts — Drop all application tables, re-run migrations, and re-seed. - * - * Intended for local development only. Never run against production. - * - * Usage: - * DATABASE_URL=postgres://... npx tsx packages/db/src/reset.ts - * - * GRO-2139: the entire drop→migrate→seed chain runs inside a single - * Postgres advisory lock (SEED_ADVISORY_LOCK_KEY) so a concurrent - * `seed.ts` (e.g. the dev `seed-test-data-*` Job being recreated at - * the top of the hour) cannot interleave between `reset.ts` (DROP) - * and `seed.ts` (TRUNCATE+insert) and collide on `invoices_pkey`. - * - * Why this matters: `seed.ts` derives every primary key from a single - * shared Mulberry32 PRNG seeded with 42 (see `createPrng(42)` and - * `uuid()` in seed.ts). Two concurrent same-profile seeders therefore - * emit *identical* ids for the same logical row, and any moment - * between a concurrent `seed.ts` TRUNCATE and INSERT is exactly the - * window in which the second seeder's INSERT can hit a pkey already - * taken by the first. Pre-GRO-2123 this raced unconditionally; - * GRO-2123 added the advisory lock around `runSeedBody` but left - * `reset.ts` and `drizzle-kit migrate` outside the lock. This script - * now wraps the *whole* chain in the same lock: `withSeedAdvisoryLock` - * pins the lock to one reserved session and the DROP → migrate → seed - * work runs on the rest of the pool, so the lock guarantees mutual - * exclusion against any concurrent seeder for the entire chain. - * - * See: groombook/infra `apps/base/reset-cronjob.yaml` (CronJob) and - * `apps/base/seed-job.yaml` (one-shot Job) — both invoke the same - * `seed.ts` code path on the same database in `groombook-dev`. - */ -import postgres from "postgres"; -import { drizzle } from "drizzle-orm/postgres-js"; -import { migrate } from "drizzle-orm/postgres-js/migrator"; -import { fileURLToPath } from "node:url"; -import { dirname, resolve } from "node:path"; -import * as schema from "./schema.js"; -import { - SEED_ADVISORY_LOCK_KEY, - withSeedAdvisoryLock, - getProfile, - runSeedBody, - profiles, -} from "./seed.js"; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); -const MIGRATIONS_FOLDER = resolve(__dirname, "../migrations"); - -async function reset() { - const url = process.env.DATABASE_URL; - if (!url) { - console.error("DATABASE_URL is not set"); - process.exit(1); - } - - if ( - process.env.NODE_ENV === "production" && - process.env.ALLOW_RESET !== "true" - ) { - console.error( - "[FATAL] db:reset must not be run in production without ALLOW_RESET=true.", - ); - process.exit(1); - } - - // Pool sizing is load-bearing here. `withSeedAdvisoryLock` does - // `pool.reserve()` to pin the advisory lock to one dedicated session - // (a session-level lock released on a *different* pooled connection is - // a no-op), and the DROP / migrate / seed work then runs on the - // *remaining* pooled connections. The lock provides mutual exclusion - // across processes regardless of how many connections the work uses — - // it does NOT require the work to share the lock's session. - // - // Therefore `max` must be ≥ 2: 1 reserved for the lock + ≥1 free for - // the work. `max: 1` would let `reserve()` consume the only connection - // and every query inside the callback would block forever waiting for - // a connection that never frees (connection-starvation deadlock). We - // use `max: 6` to match `seed()`'s headroom (1 reserved + 5 work). - const client = postgres(url, { max: 6 }); - const db = drizzle(client, { schema }); - - try { - await withSeedAdvisoryLock(client, async () => { - console.log("Dropping all application tables...\n"); - - // Drop dependencies (tables) first - await client` - DO $$ DECLARE - r RECORD; - BEGIN - FOR r IN ( - SELECT tablename FROM pg_tables - WHERE schemaname = 'public' - ) LOOP - EXECUTE 'DROP TABLE IF EXISTS public.' || quote_ident(r.tablename) || ' CASCADE'; - END LOOP; - END $$; - `; - - // Drop custom enums - await client` - DO $$ DECLARE - r RECORD; - BEGIN - FOR r IN ( - SELECT typname FROM pg_type - WHERE typtype = 'e' AND typnamespace = ( - SELECT oid FROM pg_namespace WHERE nspname = 'public' - ) - ) LOOP - EXECUTE 'DROP TYPE IF EXISTS ' || quote_ident(r.typname) || ' CASCADE'; - END LOOP; - END $$; - `; - - // Drop the drizzle migrations tracking table - await client`DROP TABLE IF EXISTS drizzle.__drizzle_migrations CASCADE`; - await client`DROP SCHEMA IF EXISTS drizzle CASCADE`; - - console.log("✓ All tables and enums dropped\n"); - - console.log("Running migrations..."); - await migrate(db, { migrationsFolder: MIGRATIONS_FOLDER }); - console.log("✓ Migrations applied\n"); - - console.log("Seeding database..."); - const profile = getProfile(); - const cfg = profiles[profile]; - await runSeedBody(client, db, profile, cfg); - }); - - console.log( - `\n✓ Reset complete (advisory lock key=0x${SEED_ADVISORY_LOCK_KEY.toString(16)})`, - ); - } finally { - await client.end(); - } -} - -reset().catch((err) => { - console.error("Reset failed:", err); - process.exit(1); -}); +LyoqCiAqIHJlc2V0LnRzIOKAlCBEcm9wIGFsbCBhcHBsaWNhdGlvbiB0YWJsZXMsIHJlLXJ1biBtaWdyYXRpb25zLCBhbmQgcmUtc2VlZC4KICoKICogSW50ZW5kZWQgZm9yIGxvY2FsIGRldmVsb3BtZW50IG9ubHkuIE5ldmVyIHJ1biBhZ2FpbnN0IHByb2R1Y3Rpb24uCiAqCiAqIFVzYWdlOgogKiAgIERBVEFCQVNFX1VSTD1wb3N0Z3JlczovLy4uLiBucHggdHN4IHBhY2thZ2VzL2RiL3NyYy9yZXNldC50cwogKgogKiBHUk8tMjEzOTogdGhlIGVudGlyZSBkcm9w4oaSbWlncmF0ZeKGknNlZWQgY2hhaW4gcnVucyBpbnNpZGUgYSBzaW5nbGUKICogUG9zdGdyZXMgYWR2aXNvcnkgbG9jayAoU0VFRF9BRFZJU09SWV9MT0NLX0tFWSkgc28gYSBjb25jdXJyZW50CiAqIGBzZWVkLnRzYCAoZS5nLiB0aGUgZGV2IGBzZWVkLXRlc3QtZGF0YS0qYCBKb2IgYmVpbmcgcmVjcmVhdGVkIGF0CiAqIHRoZSB0b3Agb2YgdGhlIGhvdXIpIGNhbm5vdCBpbnRlcmxlYXZlIGJldHdlZW4gYHJlc2V0LnRzYCAoRFJPUCkKICogYW5kIGBzZWVkLnRzYCAoVFJVTkNBVEUraW5zZXJ0KSBhbmQgY29sbGlkZSBvbiBgaW52b2ljZXNfcGtleWAuCiAqCiAqIFdoeSB0aGlzIG1hdHRlcnM6IGBzZWVkLnRzYCBkZXJpdmVzIGV2ZXJ5IHByaW1hcnkga2V5IGZyb20gYSBzaW5nbGUKICogc2hhcmVkIE11bGJlcnJ5MzIgUFJORyBzZWVkZWQgd2l0aCA0MiAoc2VlIGBjcmVhdGVQcm5nKDQyKWAgYW5kCiAqIGB1dWlkKClgIGluIHNlZWQudHMpLiBUd28gY29uY3VycmVudCBzYW1lLXByb2ZpbGUgc2VlZGVycyB0aGVyZWZvcmUKICogZW1pdCAqaWRlbnRpY2FsKiBpZHMgZm9yIHRoZSBzYW1lIGxvZ2ljYWwgcm93LCBhbmQgYW55IG1vbWVudAogKiBiZXR3ZWVuIGEgY29uY3VycmVudCBgc2VlZC50c2AgVFJVTkNBVEUgYW5kIElOU0VSVCBpcyBleGFjdGx5IHRoZQogKiB3aW5kb3cgaW4gd2hpY2ggdGhlIHNlY29uZCBzZWVkZXIncyBJTlNFUlQgY2FuIGhpdCBhIHBrZXkgYWxyZWFkeQogKiB0YWtlbiBieSB0aGUgZmlyc3QuIFByZS1HUk8tMjEyMyB0aGlzIHJhY2VkIHVuY29uZGl0aW9uYWxseTsKICogR1JPLTIxMjMgYWRkZWQgdGhlIGFkdmlzb3J5IGxvY2sgYXJvdW5kIGBydW5TZWVkQm9keWAgYnV0IGxlZnQKICogYHJlc2V0LnRzYCBhbmQgYGRyaXp6bGUta2l0IG1pZ3JhdGVgIG91dHNpZGUgdGhlIGxvY2suIFRoaXMgc2NyaXB0CiAqIG5vdyB3cmFwcyB0aGUgKndob2xlKiBjaGFpbiBpbiB0aGUgc2FtZSBsb2NrOiBgd2l0aFNlZWRBZHZpc29yeUxvY2tgCiAqIHBpbnMgdGhlIGxvY2sgdG8gb25lIHJlc2VydmVkIHNlc3Npb24gYW5kIHRoZSBEUk9QIOKGkiBtaWdyYXRlIOKGkiBzZWVkCiAqIHdvcmsgcnVucyBvbiB0aGUgcmVzdCBvZiB0aGUgcG9vbCwgc28gdGhlIGxvY2sgZ3VhcmFudGVlcyBtdXR1YWwKICogZXhjbHVzaW9uIGFnYWluc3QgYW55IGNvbmN1cnJlbnQgc2VlZGVyIGZvciB0aGUgZW50aXJlIGNoYWluLgogKgogKiBTZWU6IGdyb29tYm9vay9pbmZyYSBgYXBwcy9iYXNlL3Jlc2V0LWNyb25qb2IueWFtbGAgKENyb25Kb2IpIGFuZAogKiBgYXBwcy9iYXNlL3NlZWQtam9iLnlhbWxgIChvbmUtc2hvdCBKb2IpIOKAlCBib3RoIGludm9rZSB0aGUgc2FtZQogKiBgc2VlZC50c2AgY29kZSBwYXRoIG9uIHRoZSBzYW1lIGRhdGFiYXNlIGluIGBncm9vbWJvb2stZGV2YC4KICoKICogR1JPLTI2NzI6IG1pZ3JhdGUoKSBmcm9tIGRyaXp6bGUtb3JtL3Bvc3RncmVzLWpzL21pZ3JhdG9yIGhhcyBhCiAqIGhpZ2gtd2F0ZXItbWFyayBidWcgKEdSTy0xOTk5LzIwMzMpIHRoYXQgc2lsZW50bHkgc2tpcHMgbWlncmF0aW9ucwogKiB3aG9zZSBgd2hlbmAgdGltZXN0YW1wIGlzIOKJpCB0aGUgbW9zdCByZWNlbnRseSBhcHBsaWVkIG1pZ3JhdGlvbidzCiAqIGNyZWF0ZWRfYXQuIE1pZ3JhdGlvbnMgMDAwMSwgMDAwMywgMDAxMCwgMDAxMSBoYXZlIHN0YWxlIDIwMjUtZXJhCiAqIGB3aGVuYCB2YWx1ZXMgdGhhdCBmYWxsIGJlbG93IHRoZSB3YXRlcm1hcmsgc2V0IGJ5IG1pZ3JhdGlvbiAwMDAwCiAqIG9uIGEgZnJlc2ggREIsIGNhdXNpbmcgdGhlbSB0byBiZSBza2lwcGVkLiBTaW5jZSBtaWdyYXRlKCkgd3JhcHMKICogYWxsIFNRTCBpbiBhIHNpbmdsZSB0cmFuc2FjdGlvbiwgYW55IGRvd25zdHJlYW0gbWlncmF0aW9uIHRoYXQKICogZGVwZW5kcyBvbiBhIHNraXBwZWQgbWlncmF0aW9uJ3MgdGFibGVzL2NvbHVtbnMgY2F1c2VzIGEgZnVsbAogKiByb2xsYmFjayDigJQgaW5jbHVkaW5nIG1pZ3JhdGlvbiAwMDAwJ3Mgc3RhZmYgYW5kIHNlcnZpY2VzIHRhYmxlcy4KICoKICogRml4OiB1c2UgYGRyaXp6bGUta2l0IG1pZ3JhdGVgIChoYXNoLWJhc2VkLCBzYW1lIGFzIHRoZSBLOHMgbWlncmF0ZQogKiBKb2IpIGluc3RlYWQgb2YgdGhlIGRyaXp6bGUtb3JtIHJ1bnRpbWUgbWlncmF0b3IuIGRyaXp6bGUta2l0IGNoZWNrcwogKiBlYWNoIG1pZ3JhdGlvbidzIGhhc2ggYWdhaW5zdCB0aGUgbGVkZ2VyLCBzbyBpdCBhcHBsaWVzIGV2ZXJ5CiAqIHVuaGFzaGVkIG1pZ3JhdGlvbiByZWdhcmRsZXNzIG9mIGl0cyBgd2hlbmAgdGltZXN0YW1wLgogKi8KaW1wb3J0IHBvc3RncmVzIGZyb20gInBvc3RncmVzIjsKaW1wb3J0IHsgZHJpenpsZSB9IGZyb20gImRyaXp6bGUtb3JtL3Bvc3RncmVzLWpzIjsKaW1wb3J0IHsgZXhlY1N5bmMgfSBmcm9tICJub2RlOmNoaWxkX3Byb2Nlc3MiOwppbXBvcnQgeyBmaWxlVVJMVG9QYXRoIH0gZnJvbSAibm9kZTp1cmwiOwppbXBvcnQgeyBkaXJuYW1lLCByZXNvbHZlIH0gZnJvbSAibm9kZTpwYXRoIjsKaW1wb3J0ICogYXMgc2NoZW1hIGZyb20gIi4vc2NoZW1hLmpzIjsKaW1wb3J0IHsKICBTRUVEX0FEVklTT1JZX0xPQ0tfS0VZLAogIHdpdGhTZWVkQWR2aXNvcnlMb2NrLAogIGdldFByb2ZpbGUsCiAgcnVuU2VlZEJvZHksCiAgcHJvZmlsZXMsCn0gZnJvbSAiLi9zZWVkLmpzIjsKCmNvbnN0IF9fZmlsZW5hbWUgPSBmaWxlVVJMVG9QYXRoKGltcG9ydC5tZXRhLnVybCk7CmNvbnN0IF9fZGlybmFtZSA9IGRpcm5hbWUoX19maWxlbmFtZSk7Cgphc3luYyBmdW5jdGlvbiByZXNldCgpIHsKICBjb25zdCB1cmwgPSBwcm9jZXNzLmVudi5EQVRBQkFTRV9VUkw7CiAgaWYgKCF1cmwpIHsKICAgIGNvbnNvbGUuZXJyb3IoIkRBVEFCQVNFX1VSTCBpcyBub3Qgc2V0Iik7CiAgICBwcm9jZXNzLmV4aXQoMSk7CiAgfQoKICBpZiAoCiAgICBwcm9jZXNzLmVudi5OT0RFX0VOViA9PT0gInByb2R1Y3Rpb24iICYmCiAgICBwcm9jZXNzLmVudi5BTExPV19SRVNFVCAhPT0gInRydWUiCiAgKSB7CiAgICBjb25zb2xlLmVycm9yKAogICAgICAiW0ZBVEFMXSBkYjpyZXNldCBtdXN0IG5vdCBiZSBydW4gaW4gcHJvZHVjdGlvbiB3aXRob3V0IEFMTE9XX1JFU0VUPXRydWUuIiwKICAgICk7CiAgICBwcm9jZXNzLmV4aXQoMSk7CiAgfQoKICAvLyBQb29sIHNpemluZyBpcyBsb2FkLWJlYXJpbmcgaGVyZS4gYHdpdGhTZWVkQWR2aXNvcnlMb2NrYCBkb2VzCiAgLy8gYHBvb2wucmVzZXJ2ZSgpYCB0byBwaW4gdGhlIGFkdmlzb3J5IGxvY2sgdG8gb25lIGRlZGljYXRlZCBzZXNzaW9uCiAgLy8gKGEgc2Vzc2lvbi1sZXZlbCBsb2NrIHJlbGVhc2VkIG9uIGEgKmRpZmZlcmVudCogcG9vbGVkIGNvbm5lY3Rpb24gaXMKICAvLyBhIG5vLW9wKSwgYW5kIHRoZSBEUk9QIC8gbWlncmF0ZSAvIHNlZWQgd29yayB0aGVuIHJ1bnMgb24gdGhlCiAgLy8gKnJlbWFpbmluZyogcG9vbGVkIGNvbm5lY3Rpb25zLiBUaGUgbG9jayBwcm92aWRlcyBtdXR1YWwgZXhjbHVzaW9uCiAgLy8gYWNyb3NzIHByb2Nlc3NlcyByZWdhcmRsZXNzIG9mIGhvdyBtYW55IGNvbm5lY3Rpb25zIHRoZSB3b3JrIHVzZXMg4oCUCiAgLy8gaXQgZG9lcyBOT1QgcmVxdWlyZSB0aGUgd29yayB0byBzaGFyZSB0aGUgbG9jaydzIHNlc3Npb24uCiAgLy8KICAvLyBUaGVyZWZvcmUgYG1heGAgbXVzdCBiZSDiiaUgMjogMSByZXNlcnZlZCBmb3IgdGhlIGxvY2sgKyDiiaUxIGZyZWUgZm9yCiAgLy8gdGhlIHdvcmsuIGBtYXg6IDFgIHdvdWxkIGxldCBgcmVzZXJ2ZSgpYCBjb25zdW1lIHRoZSBvbmx5IGNvbm5lY3Rpb24KICAvLyBhbmQgZXZlcnkgcXVlcnkgaW5zaWRlIHRoZSBjYWxsYmFjayB3b3VsZCBibG9jayBmb3JldmVyIHdhaXRpbmcgZm9yCiAgLy8gYSBjb25uZWN0aW9uIHRoYXQgbmV2ZXIgZnJlZXMgKGNvbm5lY3Rpb24tc3RhcnZhdGlvbiBkZWFkbG9jaykuIFdlCiAgLy8gdXNlIGBtYXg6IDZgIHRvIG1hdGNoIGBzZWVkKClgJ3MgaGVhZHJvb20gKDEgcmVzZXJ2ZWQgKyA1IHdvcmspLgogIGNvbnN0IGNsaWVudCA9IHBvc3RncmVzKHVybCwgeyBtYXg6IDYgfSk7CiAgY29uc3QgZGIgPSBkcml6emxlKGNsaWVudCwgeyBzY2hlbWEgfSk7CgogIHRyeSB7CiAgICBhd2FpdCB3aXRoU2VlZEFkdmlzb3J5TG9jayhjbGllbnQsIGFzeW5jICgpID0+IHsKICAgICAgY29uc29sZS5sb2coIkRyb3BwaW5nIGFsbCBhcHBsaWNhdGlvbiB0YWJsZXMuLi5cbiIpOwoKICAgICAgLy8gRHJvcCBkZXBlbmRlbmNpZXMgKHRhYmxlcykgZmlyc3QKICAgICAgYXdhaXQgY2xpZW50YAogICAgICAgIERPICQkIERFQ0xBUkUKICAgICAgICAgIHIgUkVDT1JEOwogICAgICAgIEJFR0lOCiAgICAgICAgICBGT1IgciBJTiAoCiAgICAgICAgICAgIFNFTEVDVCB0YWJsZW5hbWUgRlJPTSBwZ190YWJsZXMKICAgICAgICAgICAgV0hFUkUgc2NoZW1hbmFtZSA9ICdwdWJsaWMnCiAgICAgICAgICApIExPT1AKICAgICAgICAgICAgRVhFQ1VURSAnRFJPUCBUQUJMRSBJRiBFWElTVFMgcHVibGljLicgfHwgcXVvdGVfaWRlbnQoci50YWJsZW5hbWUpIHx8ICcgQ0FTQ0FERSc7CiAgICAgICAgICBFTkQgTE9PUDsKICAgICAgICBFTkQgJCQ7CiAgICAgIGA7CgogICAgICAvLyBEcm9wIGN1c3RvbSBlbnVtcwogICAgICBhd2FpdCBjbGllbnRgCiAgICAgICAgRE8gJCQgREVDTEFSRQogICAgICAgICAgciBSRUNPUkQ7CiAgICAgICAgQkVHSU4KICAgICAgICAgIEZPUiByIElOICgKICAgICAgICAgICAgU0VMRUNUIHR5cG5hbWUgRlJPTSBwZ190eXBlCiAgICAgICAgICAgIFdIRVJFIHR5cHR5cGUgPSAnZScgQU5EIHR5cG5hbWVzcGFjZSA9ICgKICAgICAgICAgICAgICBTRUxFQ1Qgb2lkIEZST00gcGdfbmFtZXNwYWNlIFdIRVJFIG5zcG5hbWUgPSAncHVibGljJwogICAgICAgICAgICApCiAgICAgICAgICApIExPT1AKICAgICAgICAgICAgRVhFQ1VURSAnRFJPUCBUWVBFIElGIEVYSVNUUyAnIHx8IHF1b3RlX2lkZW50KHIudHlwbmFtZSkgfHwgJyBDQVNDQURFJzsKICAgICAgICAgIEVORCBMT09QOwogICAgICAgIEVORCAkJDsKICAgICAgYDsKCiAgICAgIC8vIERyb3AgdGhlIGRyaXp6bGUgbWlncmF0aW9ucyB0cmFja2luZyB0YWJsZQogICAgICBhd2FpdCBjbGllbnRgRFJPUCBUQUJMRSBJRiBFWElTVFMgZHJpenpsZS5fX2RyaXp6bGVfbWlncmF0aW9ucyBDQVNDQURFYDsKICAgICAgYXdhaXQgY2xpZW50YERST1AgU0NIRU1BIElGIEVYSVNUUyBkcml6emxlIENBU0NBREVgOwoKICAgICAgY29uc29sZS5sb2coIuKckyBBbGwgdGFibGVzIGFuZCBlbnVtcyBkcm9wcGVkXG4iKTsKCiAgICAgIGNvbnNvbGUubG9nKCJSdW5uaW5nIG1pZ3JhdGlvbnMuLi4iKTsKICAgICAgLy8gR1JPLTI2NzI6IHVzZSBkcml6emxlLWtpdCAoaGFzaC1iYXNlZCkgaW5zdGVhZCBvZiBkcml6emxlLW9ybQogICAgICAvLyBtaWdyYXRlKCkgKGhpZ2gtd2F0ZXItbWFyaykgdG8gZ3VhcmFudGVlIGFsbCBtaWdyYXRpb25zIGFwcGx5IG9uCiAgICAgIC8vIGEgZnJlc2ggREIgcmVnYXJkbGVzcyBvZiB0aGVpciBgd2hlbmAgdGltZXN0YW1wIG9yZGVyaW5nLgogICAgICBleGVjU3luYygicG5wbSBleGVjIGRyaXp6bGUta2l0IG1pZ3JhdGUiLCB7CiAgICAgICAgc3RkaW86ICJpbmhlcml0IiwKICAgICAgICBlbnY6IHsgLi4ucHJvY2Vzcy5lbnYgfSwKICAgICAgICBjd2Q6IHJlc29sdmUoX19kaXJuYW1lLCAiLi4iKSwKICAgICAgfSk7CiAgICAgIGNvbnNvbGUubG9nKCLinJMgTWlncmF0aW9ucyBhcHBsaWVkXG4iKTsKCiAgICAgIGNvbnNvbGUubG9nKCJTZWVkaW5nIGRhdGFiYXNlLi4uIik7CiAgICAgIGNvbnN0IHByb2ZpbGUgPSBnZXRQcm9maWxlKCk7CiAgICAgIGNvbnN0IGNmZyA9IHByb2ZpbGVzW3Byb2ZpbGVdOwogICAgICBhd2FpdCBydW5TZWVkQm9keShjbGllbnQsIGRiLCBwcm9maWxlLCBjZmcpOwogICAgfSk7CgogICAgY29uc29sZS5sb2coCiAgICAgIGBcbuKckyBSZXNldCBjb21wbGV0ZSAoYWR2aXNvcnkgbG9jayBrZXk9MHgke1NFRURfQURWSVNPUllfTE9DS19LRVkudG9TdHJpbmcoMTYpfSlgLAogICAgKTsKICB9IGZpbmFsbHkgewogICAgYXdhaXQgY2xpZW50LmVuZCgpOwogIH0KfQoKcmVzZXQoKS5jYXRjaCgoZXJyKSA9PiB7CiAgY29uc29sZS5lcnJvcigiUmVzZXQgZmFpbGVkOiIsIGVycik7CiAgcHJvY2Vzcy5leGl0KDEpOwp9KTsK \ No newline at end of file diff --git a/trigger-uat-1779751324.txt b/trigger-uat-1779751324.txt deleted file mode 100644 index e69de29..0000000