Compare commits

...
Author SHA1 Message Date
BrunoGao 1ae6eb6416 feat(health): stage Contabo production migration (#360)
Adds a manual-only Contabo canary path, Traefik overlays, fail-closed migration gates, and contract tests.

Refs lingjingwanxiang/ljwx-health#390
Refs lingjingwanxiang/ljwx-health#395
2026-08-02 17:20:07 +08:00
BrunoGao 6833e1253a feat(ops): prefer cross-zone health replicas (#359)
Add zone-first and hostname-fallback soft scheduling preferences plus a deploy-gate contract test.

Refs https://github.com/lingjingwanxiang/ljwx-health/issues/390
Refs https://github.com/lingjingwanxiang/ljwx-health/issues/395
2026-08-02 09:17:13 +08:00
BrunoGao 94806b6876 Remove temporary LJWX Health legacy cleanup resources (#358)
Remove the successful one-shot cleanup hook and its ordinary least-privilege RBAC so Argo prunes the last temporary resources while preserving the audit evidence.
2026-08-01 05:15:05 +08:00
BrunoGao 96db8706b5 Delete retired LJWX Health hook orphans through GitOps (#357)
Run an exact-name, least-privilege PostSync deletion for the hook objects that Argo does not ordinary-prune. Keep temporary cleanup RBAC tracked for a final GitOps prune after observed deletion.
2026-08-01 05:05:03 +08:00
BrunoGao 188cd35e41 Adopt retired LJWX Health hooks for GitOps pruning (#356)
Temporarily adopt the exact retired hook object names as empty, permissionless ordinary resources so Argo CD can track and prune them through a follow-up GitOps revision.
2026-08-01 04:59:15 +08:00
BrunoGao 2b2cd69428 Retire legacy LJWX Health PostgreSQL schema paths (#355)
Retire the duplicate PostgreSQL SQL ConfigMap/init/repair path after verified production adoption, qualified restore, and the repaired N/N-1/N compatibility window. Preserve ClickHouse SQL, immutable audit history, backups, and signed digest-only schema delivery.
2026-08-01 04:49:03 +08:00
ljwx-release-bot 6cee4f8d41 chore(ljwx-health): promote ee2e655bd137 sha256:4cb7ffe007a4 2026-07-31 20:37:31 +00:00
ljwx-release-bot 08936fd1d7 chore(ljwx-health): promote ddddd7c6f37b sha256:09a84ab83ab0 2026-07-31 20:28:46 +00:00
ljwx-release-bot 1c86d14a90 chore(ljwx-health): promote ee2e655bd137 sha256:4cb7ffe007a4 2026-07-31 20:16:42 +00:00
BrunoGao 0f7f812907 ops(health): deactivate completed restore drill (#354) 2026-08-01 03:40:53 +08:00
ljwx-release-bot cad2b157a8 chore(ljwx-health): promote ddddd7c6f37b sha256:09a84ab83ab0 2026-07-31 19:38:00 +00:00
ljwx-release-bot 989ef61e8e chore(ljwx-health): promote cfe3a8f9c773 sha256:f53e6e3f0c8e 2026-07-31 19:25:31 +00:00
BrunoGao 7345fa7342 ops(health): restore latest qualified backup (#353) 2026-08-01 03:10:11 +08:00
BrunoGao 45256de506 fix(restore): authorize signed pre-adoption plan (#352) 2026-08-01 02:53:14 +08:00
BrunoGao 9f4c1fda01 fix(restore): allow exact k3s API endpoint (#351) 2026-08-01 02:35:42 +08:00
BrunoGao 8a7e6469d0 fix(restore): prepare volume modes before ownership (#350) 2026-08-01 02:23:29 +08:00
BrunoGao 6b220a414e ops(health): run pre-adoption schema restore drill (#349) 2026-08-01 02:15:21 +08:00
BrunoGao 16d1c987cd ops(postgres): schedule post-adoption differential backup (#347) 2026-08-01 02:08:51 +08:00
BrunoGao 5772d2f3d0 feat(restore): add application-level schema restore drill (#348)
* ops(health): smoke immutable app after schema restore

* ci: silence trap-only shellcheck paths
2026-08-01 02:07:42 +08:00
ljwx-release-bot 067a6baf56 chore(ljwx-health): promote ddddd7c6f37b sha256:09a84ab83ab0 2026-07-31 17:52:24 +00:00
ljwx-release-bot bedb1058f9 chore(ljwx-health): promote cfe3a8f9c773 sha256:f53e6e3f0c8e 2026-07-31 16:23:23 +00:00
ljwx-release-bot 068ef334f5 chore(ljwx-health): promote 5797ba6d4463 sha256:3aeddea39f43 2026-07-31 15:28:09 +00:00
ljwx-release-bot 08ed4e22c6 chore(ljwx-health): promote 7566915f3417 sha256:9ef7bfe70e8b 2026-07-31 14:40:21 +00:00
ljwx-release-bot 75f392df7a chore(ljwx-health): promote d234e1c247e3 sha256:2a0284a923f2 2026-07-31 13:38:25 +00:00
ljwx-release-bot 75c0c4bad3 chore(ljwx-health): promote 3e1c56e79981 sha256:ae6b78fe2358 2026-07-31 12:27:20 +00:00
BrunoGao b236ed97ce chore(ljwx-health): activate restore drill read boundary (#346) 2026-07-31 20:02:18 +08:00
BrunoGao 7de60242c5 feat(ljwx-health): project adopted schema release facts (#345) 2026-07-31 19:48:23 +08:00
BrunoGao 8dd9827602 fix(ljwx-health): resume completed schema upgrade retries (#344)
Verify the complete signed schema before retrying adoption-plan upgrades and report exact fingerprint mismatches.
2026-07-31 18:43:21 +08:00
BrunoGao fc9dbaa9d1 fix(cluster): preserve registry-specific pull credentials (#343)
Sync Harbor and GHCR Docker credentials from distinct source Secrets, avoid client-side secret annotations, and preserve Harbor-only default ServiceAccount binding.
2026-07-31 18:33:34 +08:00
BrunoGao f35f8a4c00 fix(ljwx-health): stage signed schema release before adoption (#342)
Create the immutable signed schema Release ConfigMap at PreSync wave -3 and preserve the ordering in future promotions.
2026-07-31 11:29:41 +08:00
BrunoGao d02154f0d0 fix(ljwx-health): order schema bootstrap before PreSync job (#341)
Create bootstrap ServiceAccount and RBAC as wave -3 PreSync hooks before the wave -2 schema adoption Job.
2026-07-31 11:23:31 +08:00
ljwx-release-bot 65fac99336 chore(ljwx-health): promote 88dd0689d34a sha256:a07878a50086 2026-07-31 03:19:07 +00:00
BrunoGao 9d331f95c2 feat(ljwx-health): add isolated schema restore drill foundation (#340)
* feat(ljwx-health): add isolated schema restore drill foundation

* fix(ljwx-health): verify Cosign v3 custom bindings

* test(ljwx-health): cover production binding envelope

* fix(ljwx-health): isolate restore drill API token

* fix(ljwx-health): split restore drill cross-namespace RBAC

* fix(ljwx-health): annotate trap-only cleanup
2026-07-31 10:21:56 +08:00
BrunoGao b0a034b238 fix(schema): bootstrap migrator with signed runner image (#339)
Refs #335 and lingjingwanxiang/ljwx-health#713.

Use only the signed schema-runner image and resource-scoped Kubernetes API access for the one-time migrator credential bootstrap.
2026-07-31 09:04:38 +08:00
BrunoGao 1aa41d4c34 fix(postgres): make backup verification shell-free (#338)
Refs #335 and lingjingwanxiang/ljwx-health#713.

Replace the failed report pod with shell-free verification and preserve the successful pgBackRest recovery chain.
2026-07-31 08:52:07 +08:00
BrunoGao 3ad88f6093 ops(postgres): add Phase 5 pre-adoption backup gate (#337)
Refs #335 and lingjingwanxiang/ljwx-health#713.

Run and report the required GitOps-owned pgBackRest backup before schema adoption. The later cleanup PR removes this one-time Job after evidence is archived.
2026-07-31 08:45:03 +08:00
BrunoGao 7f8f3bc42a feat(ljwx-health): consume signed schema bundles via GitOps (#336)
Closes #335 foundation slice
Refs lingjingwanxiang/ljwx-health#713

The schema package remains dormant until a validated v2 Release receipt activates it.
2026-07-31 08:20:20 +08:00
BrunoGao c8b3d519ed docs(db): classify LJWX Health schema bundle drift (#334)
Documents canonical-source drift classification for LJWX Health phase #709.

Refs lingjingwanxiang/ljwx-health#709.
2026-07-30 21:58:43 +08:00
ljwx-release-bot 0a1e82057c chore(ljwx-health): promote d909ba7fc8a1 sha256:016ae017e2cd 2026-07-30 12:59:50 +00:00
BrunoGao 4f9185ff41 feat: enforce ljwx-health digest-only promotion (#332)
Refs #332
2026-07-30 20:21:36 +08:00
v3-lite-bot 1a4ef9a2a0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@fa70c01 -> sha256:6e095b75ea62 2026-07-30 08:01:19 +00:00
v3-lite-bot 3fbe0b1e5f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@27fcb11 -> sha256:83b5947d6405 2026-07-30 07:00:33 +00:00
v3-lite-bot 7c9d0f2b84 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@faf6e6f -> sha256:d496b3284df9 2026-07-30 06:02:12 +00:00
v3-lite-bot e689ca78b0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5048b61 -> sha256:d9571ecf6c45 2026-07-30 05:09:52 +00:00
v3-lite-bot fb2cd322ad chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@75a9397 -> sha256:bf50c9e95d4c 2026-07-30 04:20:56 +00:00
v3-lite-bot ba889e2764 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c1127e7 -> sha256:a06a79d556c9 2026-07-30 03:33:06 +00:00
v3-lite-bot 4ac6141c62 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@66a8663 -> sha256:f89fe8e3ebe3 2026-07-30 02:42:37 +00:00
v3-lite-bot ed803105c0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@26b2bdd -> sha256:17f51913a188 2026-07-30 01:48:16 +00:00
v3-lite-bot 04f4aca415 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f1480a9 -> sha256:0c46d4fcd04e 2026-07-30 00:50:31 +00:00
v3-lite-bot 2207d9bb5e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@634b0f4 -> sha256:050134f9fc33 2026-07-29 23:55:46 +00:00
v3-lite-bot dd8dfdba8b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@765097c -> sha256:979f40d2b914 2026-07-29 22:56:39 +00:00
v3-lite-bot a8b0161956 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e0e0072 -> sha256:3f71fc647300 2026-07-29 22:06:50 +00:00
v3-lite-bot 6dd3ac5f7e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9cb90d2 -> sha256:49da0c00412e 2026-07-29 21:15:39 +00:00
v3-lite-bot d844c543b8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@6485a56 -> sha256:cc347eb33c9d 2026-07-29 20:30:26 +00:00
v3-lite-bot d5dcfa58f9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9bdad75 -> sha256:cb38f81baa0c 2026-07-29 19:34:55 +00:00
v3-lite-bot 9b19aeb43e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@74b2c4f -> sha256:92c98d5c2acd 2026-07-29 18:50:32 +00:00
v3-lite-bot 8242c81420 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@808c6de -> sha256:e7f6bc4b240c 2026-07-29 18:04:21 +00:00
v3-lite-bot b3ed59443a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e8697cc -> sha256:0a8515bf8523 2026-07-29 17:12:54 +00:00
v3-lite-bot ad7e30d683 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9e2d77f -> sha256:6baad7de6b55 2026-07-29 16:26:34 +00:00
v3-lite-bot d82891d8aa chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@fbf0d1e -> sha256:2b1454c13a72 2026-07-29 15:31:44 +00:00
v3-lite-bot d2e0f358b6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@cc85b02 -> sha256:e1640273bc43 2026-07-29 14:37:10 +00:00
v3-lite-bot 08496d14fb chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9b12e85 -> sha256:05a0acba8cc8 2026-07-29 13:43:04 +00:00
v3-lite-bot 02f1dea1a7 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7fc5ccc -> sha256:70e680ddd0f6 2026-07-29 12:52:28 +00:00
v3-lite-bot d8486de967 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@dc1efe6 -> sha256:f16f51b10c2c 2026-07-29 12:03:44 +00:00
v3-lite-bot cecd98e8c2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4054c7f -> sha256:cbaa2cb568ad 2026-07-29 11:16:29 +00:00
v3-lite-bot 9e94d56db6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3572d8e -> sha256:517ba0f28023 2026-07-29 10:31:12 +00:00
v3-lite-bot 9e259fa1de chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0d99e60 -> sha256:a1ae4ff92e75 2026-07-29 09:42:28 +00:00
v3-lite-bot 447f224937 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3d80ba9 -> sha256:b07ab162b835 2026-07-29 08:52:23 +00:00
v3-lite-bot f04706c825 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@096dbd3 -> sha256:e283d8e12546 2026-07-29 05:41:09 +00:00
v3-lite-bot 87d41b083e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3ab4c1e -> sha256:c7c1b420200f 2026-07-29 04:59:03 +00:00
v3-lite-bot f318682ca2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@74443f1 -> sha256:97be59696d3f 2026-07-29 04:18:12 +00:00
v3-lite-bot 1834f3805b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7353209 -> sha256:9036a1046aff 2026-07-29 03:27:23 +00:00
v3-lite-bot 64fd330886 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@86d599f -> sha256:d595a32f0a55 2026-07-29 02:39:18 +00:00
v3-lite-bot 77793e4dac chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@36e7793 -> sha256:c065309adf8d 2026-07-29 02:00:48 +00:00
v3-lite-bot d677a9ec66 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@be9d80d -> sha256:3e94d721762b 2026-07-29 01:11:07 +00:00
v3-lite-bot 044f33475e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@854eacd -> sha256:a9eb4020bf95 2026-07-29 00:22:25 +00:00
v3-lite-bot 490845aa76 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@de0fd7e -> sha256:789b22f599a0 2026-07-28 23:25:23 +00:00
v3-lite-bot 79556e8bba chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7098703 -> sha256:9e3879d1ab30 2026-07-28 22:53:28 +00:00
v3-lite-bot 6e65776e26 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e856cf1 -> sha256:8aa8709ddb38 2026-07-28 22:05:36 +00:00
BrunoGao 512ada635a feat(ljwx-health): deploy blueprint operations schema (#331)
* feat(ljwx-health): deploy blueprint operations schema

* chore: retrigger deploy gate
2026-07-29 05:50:42 +08:00
v3-lite-bot 5f2ab3b635 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@394a936 -> sha256:7bd0bded6264 2026-07-28 21:10:56 +00:00
BrunoGao 6a715dd846 feat(ljwx-health): deploy IA17 tenant blueprint schema (#330)
* feat(ljwx-health): deploy tenant blueprint schema

* docs(ljwx-health): trace IA17 schema source
2026-07-29 04:22:13 +08:00
v3-lite-bot 9932d8349a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f69fff1 -> sha256:6bb5eef238b0 2026-07-28 20:10:13 +00:00
v3-lite-bot 8e46e14168 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@84569a0 -> sha256:a7899158ede8 2026-07-28 19:11:16 +00:00
v3-lite-bot abe6e54148 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@6cc7f42 -> sha256:79f56fcdac8e 2026-07-28 18:30:30 +00:00
v3-lite-bot 547713ff17 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e620b9d -> sha256:dd8734d61f48 2026-07-28 18:09:02 +00:00
v3-lite-bot 803e2a4e28 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@59fad37 -> sha256:a94ef91d0988 2026-07-28 17:51:45 +00:00
v3-lite-bot 862f865e41 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1d1d72c -> sha256:0d2c218227ea 2026-07-28 17:40:20 +00:00
v3-lite-bot 06327d805a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@fc33a85 -> sha256:526e277ac2cc 2026-07-28 17:27:20 +00:00
v3-lite-bot c889e4ea50 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@be87c4f -> sha256:cbff807db78b 2026-07-28 17:16:10 +00:00
v3-lite-bot 21382dae97 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@fc2de45 -> sha256:c7c893365317 2026-07-28 16:28:11 +00:00
BrunoGao 920ee609ac Merge pull request #329 from lingjingwanxiang/codex/issue-383-ia15-schema
feat(ljwx-health): deploy heartbeat current uniqueness
2026-07-28 23:42:47 +08:00
bruno 0fc8ca4838 feat(health): deploy heartbeat current uniqueness 2026-07-28 23:41:29 +08:00
v3-lite-bot 0b9753bad2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@103e3a5 -> sha256:73b66c747bfc 2026-07-28 15:05:59 +00:00
BrunoGao b0adb9d144 Merge pull request #328 from lingjingwanxiang/codex/issue-383-ia14-columnstore
fix(ljwx-health): tolerate columnstore envelope constraint
2026-07-28 22:47:35 +08:00
bruno eba40a0b32 fix(health): tolerate columnstore envelope constraint 2026-07-28 22:46:14 +08:00
BrunoGao 0efba2432d Merge pull request #327 from lingjingwanxiang/codex/issue-383-ia14-schema
feat(ljwx-health): deploy resource provenance envelope schema
2026-07-28 22:40:02 +08:00
bruno f3a7487eb0 feat(health): deploy resource provenance envelope schema 2026-07-28 22:36:50 +08:00
v3-lite-bot 764999867f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@599ece0 -> sha256:249aa65ceba9 2026-07-28 13:57:01 +00:00
v3-lite-bot 35ca653b65 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@133fd7a -> sha256:b55b960ffac4 2026-07-28 13:38:33 +00:00
BrunoGao fcdeb19fab feat(ljwx-health): deploy temporal device binding schema (#326)
Refs https://github.com/lingjingwanxiang/ljwx-health/issues/383
2026-07-28 21:23:46 +08:00
v3-lite-bot 37f4b9592c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@74d42ec -> sha256:6067e11802a7 2026-07-28 12:33:37 +00:00
BrunoGao 879aafe92c feat(ljwx-health): deploy user position lifecycle schema (#325) 2026-07-28 20:14:48 +08:00
v3-lite-bot 036d1740ba chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e3a9d0e -> sha256:8a6184e0423f 2026-07-28 11:17:37 +00:00
v3-lite-bot faeff4066e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e95c064 -> sha256:ba7accd34872 2026-07-28 10:50:44 +00:00
v3-lite-bot 7723996bec chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d558dfe -> sha256:f0200e826031 2026-07-28 09:55:41 +00:00
v3-lite-bot 0b2afcfc5d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e046b34 -> sha256:7797b182ec4f 2026-07-28 09:39:59 +00:00
BrunoGao 549e2da497 fix(ljwx-health): roll out org manager assignment schema (#324) 2026-07-28 17:06:11 +08:00
v3-lite-bot fa45ca4fd5 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b12260f -> sha256:db72df18d860 2026-07-28 08:09:51 +00:00
v3-lite-bot 32aa46c7dc chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@69b64c1 -> sha256:75d1a8b3948f 2026-07-28 07:17:42 +00:00
BrunoGao 9230fe7078 fix(ljwx-health): roll out primary membership schema (#323) 2026-07-28 15:00:56 +08:00
v3-lite-bot 5a8da59f6c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@91059b6 -> sha256:3ffd778aee5f 2026-07-28 06:00:51 +00:00
BrunoGao ec53059aa5 fix(ljwx-health): roll out health task scope (#322) 2026-07-28 13:53:33 +08:00
v3-lite-bot 3c9c6298ef chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@82445ac -> sha256:1bb619371c16 2026-07-28 05:14:40 +00:00
v3-lite-bot 3634f2a631 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ec771f8 -> sha256:8a87c397436a 2026-07-28 04:36:43 +00:00
v3-lite-bot 1e5164b2b8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5767dac -> sha256:c62c3ce06605 2026-07-27 07:47:32 +00:00
BrunoGao 35f37c1889 fix(ljwx-health): roll out device self-service scope (#321)
Refs https://github.com/lingjingwanxiang/ljwx-health/issues/383
2026-07-27 15:42:03 +08:00
BrunoGao c7dc7bb9e7 fix(ljwx-health): roll out user feedback schema (#320)
* fix(ljwx-health): roll out user feedback schema

* test(ljwx-health): gate feedback schema rollout
2026-07-27 14:34:40 +08:00
v3-lite-bot e606a39b05 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d46c5fd -> sha256:18dc41704bb1 2026-07-27 06:24:07 +00:00
v3-lite-bot b35f862a5d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0406874 -> sha256:e42f766daf68 2026-07-27 05:24:48 +00:00
v3-lite-bot 89e69a2faf chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ef910ed -> sha256:de071838ea1c 2026-07-27 04:36:48 +00:00
v3-lite-bot cc80ccb6b9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5425cde -> sha256:d390f9d99dab 2026-07-27 04:00:30 +00:00
v3-lite-bot abb2148262 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5b3e981 -> sha256:7a8f35933600 2026-07-27 03:25:36 +00:00
v3-lite-bot 6984e6fff1 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@8dc6d41 -> sha256:8cef06295325 2026-07-27 02:44:38 +00:00
v3-lite-bot 05880ca9d6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d7dece3 -> sha256:c88c215d361c 2026-07-27 01:27:08 +00:00
v3-lite-bot 37e8af7a1a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a22103e -> sha256:1be2fca1153c 2026-07-27 00:32:33 +00:00
v3-lite-bot 0ff1a7b4f9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b0b89e1 -> sha256:3470ea0aa4cd 2026-07-26 23:29:20 +00:00
v3-lite-bot 1016d04dd9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@cc0649a -> sha256:0a4dfd58da15 2026-07-26 22:44:45 +00:00
v3-lite-bot f253a06500 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c982137 -> sha256:3039b4aa2a5e 2026-07-26 21:56:32 +00:00
v3-lite-bot f7f17b9cd9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@19ff8be -> sha256:cba91f96801b 2026-07-26 21:03:14 +00:00
v3-lite-bot 7c76a83b38 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5330a2b -> sha256:2e7162600e72 2026-07-26 20:13:56 +00:00
v3-lite-bot f4a501a5e1 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b17e457 -> sha256:563727f85d58 2026-07-26 19:37:03 +00:00
v3-lite-bot da0aefe389 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@cd25554 -> sha256:9a2ff8e31788 2026-07-26 19:02:05 +00:00
v3-lite-bot 1c7f9276d0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@598161f -> sha256:fa0859cb8574 2026-07-26 18:31:11 +00:00
BrunoGao 378e359a39 fix(ljwx-health): apply external alert purpose schema (#319) 2026-07-27 01:56:11 +08:00
v3-lite-bot 259535340e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c9ad664 -> sha256:1805c9b6f67c 2026-07-26 17:42:12 +00:00
v3-lite-bot 20eb3a913c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d81358e -> sha256:cc0c3ac15571 2026-07-26 16:47:30 +00:00
v3-lite-bot 7aea121e51 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e739c4f -> sha256:adfe9c3ef541 2026-07-26 15:33:26 +00:00
v3-lite-bot 241ece5b8d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@60b0bb1 -> sha256:95df25e788d9 2026-07-26 14:43:42 +00:00
v3-lite-bot 9ccaab082e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@029da89 -> sha256:f03dc3e6a7f3 2026-07-26 14:05:12 +00:00
v3-lite-bot cfcbf9da77 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@10e63f1 -> sha256:358a109173b0 2026-07-26 13:13:08 +00:00
v3-lite-bot d08f1ac62f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@432c0c1 -> sha256:bc80b32e93ff 2026-07-26 12:30:31 +00:00
v3-lite-bot e153ebbee0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@702660d -> sha256:0c176d54ca5e 2026-07-26 11:51:10 +00:00
v3-lite-bot eb786d85d9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c32c38a -> sha256:bbf6dc860abe 2026-07-26 11:12:37 +00:00
v3-lite-bot a1a8ccc81e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c380233 -> sha256:09775f83ce08 2026-07-26 10:33:03 +00:00
v3-lite-bot d291674d4b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@26922ca -> sha256:a6a5d1941a31 2026-07-26 09:53:45 +00:00
v3-lite-bot 8ede28e8b3 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c26d107 -> sha256:a4cb7878798c 2026-07-26 09:16:18 +00:00
v3-lite-bot d935654dda chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@607807f -> sha256:a5e385f124b7 2026-07-26 08:29:44 +00:00
v3-lite-bot 3a59f67718 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a9daff3 -> sha256:2a023299e437 2026-07-26 07:48:32 +00:00
v3-lite-bot 028f854d48 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@93dd3fc -> sha256:8a56cbdf07fe 2026-07-26 07:09:15 +00:00
v3-lite-bot dadf08a252 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e277fd4 -> sha256:5f2bc49cb547 2026-07-26 06:28:04 +00:00
v3-lite-bot 6dd2957251 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a5a4173 -> sha256:66dbe0ccd23c 2026-07-26 05:44:47 +00:00
v3-lite-bot 3eeff2b713 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d9341df -> sha256:11a3b7bcdef5 2026-07-26 05:05:23 +00:00
v3-lite-bot b3e1dd8874 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@02a8004 -> sha256:827596276159 2026-07-26 04:38:58 +00:00
v3-lite-bot 4ea1f5a948 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d099372 -> sha256:7b4fc1739942 2026-07-26 03:54:21 +00:00
v3-lite-bot 0e5a4de709 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0ba77a0 -> sha256:0a87b9e5f019 2026-07-26 03:10:10 +00:00
BrunoGao c7926295e9 fix(ljwx-health): roll out NO_TRAFFIC reconciliation status (#318)
Apply the five-value reconciliation status vocabulary through the K3s GitOps schema bundle and fail closed on stale or unvalidated constraints.

Refs: https://github.com/lingjingwanxiang/ljwx-health/issues/349
2026-07-26 10:29:01 +08:00
v3-lite-bot e04b3ab6b1 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0b72b8e -> sha256:da793176abe9 2026-07-26 02:18:27 +00:00
v3-lite-bot 0e3cf92b03 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@fc56156 -> sha256:fc7635d319d9 2026-07-26 01:44:58 +00:00
v3-lite-bot 6cda8bb429 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@544d8e6 -> sha256:b9a41a98921a 2026-07-26 00:54:23 +00:00
v3-lite-bot 07966b7f09 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@26fcc48 -> sha256:4f7a5a1a2517 2026-07-24 06:54:46 +00:00
v3-lite-bot fd2193d593 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7062fac -> sha256:983cc34444f8 2026-07-24 06:28:47 +00:00
v3-lite-bot 60f89856e9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@14a9dfd -> sha256:2c84241d0411 2026-07-24 05:01:49 +00:00
v3-lite-bot 3257548c70 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@27fda3b -> sha256:537c1cd3e3d8 2026-07-24 04:18:04 +00:00
v3-lite-bot 2781ba1cb5 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@8b9c10d -> sha256:17e7ca00c6d7 2026-07-24 03:37:25 +00:00
v3-lite-bot 41a53da3e7 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b3cb690 -> sha256:fcdfe02b58bc 2026-07-24 02:55:40 +00:00
v3-lite-bot c8d3aee483 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7cf7024 -> sha256:4856529fee83 2026-07-24 02:08:14 +00:00
v3-lite-bot 7e51e69e5a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a6a778e -> sha256:b9414c8a6c8c 2026-07-24 01:29:43 +00:00
v3-lite-bot efa7311dcc chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5b91272 -> sha256:eb66c9fc704e 2026-07-24 01:11:25 +00:00
v3-lite-bot 7a1b4006d0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b638bb7 -> sha256:f712d2819072 2026-07-24 00:53:42 +00:00
v3-lite-bot 578c46db63 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@28f802a -> sha256:679b9ea3f853 2026-07-24 00:34:08 +00:00
v3-lite-bot 79fb8e0e28 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4491a54 -> sha256:0b2426c43300 2026-07-24 00:14:55 +00:00
v3-lite-bot 971d251559 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@aded320 -> sha256:6367def789bc 2026-07-23 23:53:12 +00:00
v3-lite-bot effa3ce27e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7ff94f7 -> sha256:fe1ba9967651 2026-07-23 23:32:42 +00:00
v3-lite-bot 15d3cdeac9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@04fdc0e -> sha256:45fef84baca1 2026-07-23 23:17:51 +00:00
v3-lite-bot 106416f499 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@59b426a -> sha256:abb7824a7cf0 2026-07-23 22:57:20 +00:00
v3-lite-bot 892ad95068 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@daa2db8 -> sha256:d75336af833d 2026-07-23 22:32:07 +00:00
v3-lite-bot b1d13bc483 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@453784d -> sha256:0325a9f1c266 2026-07-23 22:03:03 +00:00
v3-lite-bot bd135715be chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@484c3f1 -> sha256:e4c26e07627f 2026-07-23 21:51:29 +00:00
v3-lite-bot e9dac31682 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@47bcb0b -> sha256:202f0e0b389b 2026-07-23 21:37:21 +00:00
v3-lite-bot 71a3906451 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@852c65b -> sha256:73c4e122cf6f 2026-07-23 21:17:43 +00:00
v3-lite-bot bcc29f320d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@da58167 -> sha256:e20337850c32 2026-07-23 20:41:05 +00:00
v3-lite-bot 96d1cf5922 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@055fafe -> sha256:920149d3d811 2026-07-23 20:06:11 +00:00
v3-lite-bot e22f0b91ef chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0aaa329 -> sha256:8713662d8f5c 2026-07-23 19:30:08 +00:00
v3-lite-bot 77423cd0d2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0d6d80f -> sha256:4c711257e88e 2026-07-23 18:49:37 +00:00
v3-lite-bot 98fe72c6a6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3c8efd2 -> sha256:50304cd2666e 2026-07-23 18:06:31 +00:00
v3-lite-bot 14fb6875a0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@638577a -> sha256:e7fb50ae803f 2026-07-23 17:40:08 +00:00
v3-lite-bot b14b349305 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e6c1383 -> sha256:c5489a846255 2026-07-23 17:20:09 +00:00
v3-lite-bot 58a314591b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c3744e6 -> sha256:458e4eaeedcc 2026-07-23 17:03:25 +00:00
v3-lite-bot bbc5c1caf5 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ae9ca38 -> sha256:f38138a0680e 2026-07-23 16:45:40 +00:00
v3-lite-bot 7be4709589 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@8308cc6 -> sha256:9e702017828b 2026-07-23 16:29:03 +00:00
v3-lite-bot 314945fe9c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5189ea3 -> sha256:cccef680aae8 2026-07-23 16:18:07 +00:00
v3-lite-bot 20f8248f56 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@66bea1e -> sha256:644784994f45 2026-07-23 15:54:13 +00:00
v3-lite-bot f6fbdb7cce chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@cd2f605 -> sha256:6a7517baa57c 2026-07-23 15:26:07 +00:00
v3-lite-bot 2dba97bae0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@26d67b5 -> sha256:7e6c363b3b9b 2026-07-23 14:59:03 +00:00
v3-lite-bot ae313c0081 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b522b5d -> sha256:cad05b498b4d 2026-07-23 14:42:36 +00:00
v3-lite-bot abb95b301e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@85813dc -> sha256:019f6f6a2a61 2026-07-23 14:21:48 +00:00
v3-lite-bot f890756a4d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c67259c -> sha256:adc7d314fe88 2026-07-23 14:05:58 +00:00
v3-lite-bot f69a5809b6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b2a9a55 -> sha256:84bab36a68b6 2026-07-23 13:43:59 +00:00
v3-lite-bot 4e1280021f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c2ead4a -> sha256:b504d71ef573 2026-07-23 12:56:14 +00:00
v3-lite-bot 2b33eddb54 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@71c8512 -> sha256:6eb6413b3e07 2026-07-23 12:43:42 +00:00
v3-lite-bot 313d19e5f8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1b418e4 -> sha256:64ad3e9f59a8 2026-07-23 12:18:09 +00:00
v3-lite-bot abce4ffd0d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5831598 -> sha256:ec8c831f4724 2026-07-23 12:04:15 +00:00
v3-lite-bot f71a157b17 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c3c142c -> sha256:9109bad6c334 2026-07-23 11:35:03 +00:00
v3-lite-bot 0e0ec87ac9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@52c56ed -> sha256:35e00d04771e 2026-07-23 11:18:54 +00:00
v3-lite-bot a296a734a1 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@65ad7ea -> sha256:3dc6d61a285f 2026-07-23 10:59:19 +00:00
v3-lite-bot 3e44077278 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1ce69f1 -> sha256:68f5b70a54a9 2026-07-23 10:38:42 +00:00
v3-lite-bot 288b05df27 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3b94777 -> sha256:042d6fb1afa1 2026-07-23 10:17:52 +00:00
v3-lite-bot d969b9553a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@093a5cc -> sha256:cda555a29918 2026-07-23 09:57:29 +00:00
v3-lite-bot 6f8d51c4cc chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@09e5e3c -> sha256:545d16f085c4 2026-07-23 08:16:05 +00:00
v3-lite-bot 3cd11977cc chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@645506d -> sha256:9147b8776f4d 2026-07-23 07:46:52 +00:00
v3-lite-bot f4b43e13e7 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@85675e4 -> sha256:c39169df54fd 2026-07-23 07:18:22 +00:00
v3-lite-bot ff1f5abc06 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3b9d10c -> sha256:d872c9e36723 2026-07-23 03:49:25 +00:00
v3-lite-bot 90b7d89521 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@76d5968 -> sha256:64007e050681 2026-07-23 03:23:11 +00:00
v3-lite-bot 78593a0d0f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f6ad257 -> sha256:c56b2d0358c0 2026-07-23 03:01:57 +00:00
v3-lite-bot 47db834995 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@78e3583 -> sha256:0b4df38dd076 2026-07-23 02:44:23 +00:00
v3-lite-bot 358d1ed14b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@13bdbd6 -> sha256:9d7a8dff0679 2026-07-23 02:32:06 +00:00
v3-lite-bot 0e74cc7f5e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4ac09f5 -> sha256:4f8cbbd1930f 2026-07-23 02:17:08 +00:00
v3-lite-bot d19d4ec83a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@59d9935 -> sha256:83d02a91d228 2026-07-23 01:47:32 +00:00
v3-lite-bot c2990b1cae chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c7645ab -> sha256:b58577bd77e7 2026-07-23 01:29:35 +00:00
v3-lite-bot 6a46f19367 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a220f5c -> sha256:929dae563867 2026-07-23 01:14:30 +00:00
v3-lite-bot 6c15620ca8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@6eabd95 -> sha256:39c6ebdc3025 2026-07-23 00:56:49 +00:00
v3-lite-bot 83501132f8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3e8f2ba -> sha256:75afe77ac9ce 2026-07-23 00:38:25 +00:00
v3-lite-bot e30e3a13e7 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0f8648f -> sha256:a8c02fc2b27c 2026-07-23 00:24:45 +00:00
v3-lite-bot bfb12ac4b4 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@30c729c -> sha256:c294fe8ef32c 2026-07-23 00:05:34 +00:00
v3-lite-bot f68e754ce5 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@82f0bae -> sha256:213594de5cb2 2026-07-22 07:28:50 +00:00
v3-lite-bot 9b35c5348a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@da7012f -> sha256:ce1443955a5c 2026-07-22 03:33:37 +00:00
v3-lite-bot 7a407b8d0d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@71c4996 -> sha256:691dc23376ec 2026-07-22 02:46:44 +00:00
knowledge-publisher[bot] 2f4fee1f3e feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@71c49969d7 2026-07-22 02:36:28 +00:00
BrunoGao 12470dc7c5 fix(health): make IA-05 role migration PostgreSQL 18 compatible (#317)
Refs lingjingwanxiang/ljwx-health#383
2026-07-22 10:11:26 +08:00
BrunoGao 40ade10f6f feat(health): converge tenant navigation schema (#316) 2026-07-22 10:01:32 +08:00
v3-lite-bot a30393a3bd chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1f01e08 -> sha256:825eb9a4930e 2026-07-22 01:40:43 +00:00
knowledge-publisher[bot] ae88332b8d feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@1f01e08a30 2026-07-22 01:25:38 +00:00
knowledge-publisher[bot] cb1017c3fc feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@7b631dfa9e 2026-07-22 00:52:30 +00:00
v3-lite-bot eae432f71f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@2907d79 -> sha256:2ffd4de3e655 2026-07-22 00:50:00 +00:00
BrunoGao 8a750cb4e7 feat(ljwx-health): deploy authorization mutation grants (#315)
* feat(ljwx-health): deploy authorization mutation grants

* ci: refresh operations issue contract
2026-07-22 08:32:59 +08:00
v3-lite-bot 7593af02f9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0eb911a -> sha256:211018cb5265 2026-07-21 23:59:18 +00:00
BrunoGao c34655931f feat(ljwx-health): deploy authorization version facts (#314) 2026-07-22 07:15:10 +08:00
v3-lite-bot 8d8c732a13 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d766ba7 -> sha256:96f482d47a4b 2026-07-21 23:13:24 +00:00
BrunoGao e4936e777a fix(health): deploy authorization relation RLS schema (#313)
* fix(health): deploy authorization relation RLS schema

* fix(health): derive relation tenant keys from owners
2026-07-22 06:25:59 +08:00
v3-lite-bot 256bf61b2b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d4ea842 -> sha256:0f5af3fa871f 2026-07-21 22:25:36 +00:00
v3-lite-bot 8acc29b489 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@8498953 -> sha256:9c178e10cde8 2026-07-19 21:59:35 +00:00
knowledge-publisher[bot] 7d414e78a1 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@84989531a2 2026-07-19 21:48:30 +00:00
v3-lite-bot 4cd8109816 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d96cf95 -> sha256:23bd4bd87d21 2026-07-19 21:35:42 +00:00
knowledge-publisher[bot] 048efeece8 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@d96cf9559b 2026-07-19 21:25:16 +00:00
v3-lite-bot 056d9b4431 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1c5c2cd -> sha256:cd60d0ec89e6 2026-07-19 21:19:17 +00:00
knowledge-publisher[bot] be37ac7452 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@1c5c2cd0b4 2026-07-19 21:08:40 +00:00
v3-lite-bot a70913b502 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@08bcb56 -> sha256:070f5f122671 2026-07-19 20:58:54 +00:00
knowledge-publisher[bot] 314fc0d8df feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@08bcb565f8 2026-07-19 20:49:51 +00:00
BrunoGao 4f68d5e9eb fix(health): deploy mobile subject scope schema (#312) 2026-07-20 04:16:59 +08:00
v3-lite-bot 399575ad79 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@fe0f875 -> sha256:8e28debce69f 2026-07-19 20:10:59 +00:00
knowledge-publisher[bot] 96a117bb8d feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@fe0f875b40 2026-07-19 20:01:32 +00:00
v3-lite-bot f6e33dba07 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c4f599d -> sha256:382b96be86ce 2026-07-19 19:35:59 +00:00
knowledge-publisher[bot] 0880a6512e feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@c4f599da4a 2026-07-19 19:26:42 +00:00
v3-lite-bot 5634fa9ed2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@2dd4349 -> sha256:33d5bdef9751 2026-07-19 19:23:50 +00:00
v3-lite-bot 35c0880463 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@035afcf -> sha256:c8d1053c1998 2026-07-19 19:12:00 +00:00
knowledge-publisher[bot] 07bd50b8dc feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@2dd434991d 2026-07-19 19:06:07 +00:00
knowledge-publisher[bot] 2dbbd32b6f feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@035afcfc16 2026-07-19 19:00:55 +00:00
knowledge-publisher[bot] b0c3f625f0 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@035afcfc16 2026-07-19 18:42:54 +00:00
v3-lite-bot b641a766cf chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@035afcf -> sha256:2fb20971a0f9 2026-07-19 18:41:44 +00:00
v3-lite-bot 5d057c9a81 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e719c15 -> sha256:038c9e564b65 2026-07-19 18:06:31 +00:00
v3-lite-bot 2736b8b610 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@fc54700 -> sha256:fada9739c7db 2026-07-19 17:54:51 +00:00
knowledge-publisher[bot] 01429bfe7c feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@e719c15d6f 2026-07-19 17:52:41 +00:00
v3-lite-bot b3d05f697d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@602c7e9 -> sha256:e907337e1df6 2026-07-19 17:45:08 +00:00
knowledge-publisher[bot] eb69037821 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@fc54700367 2026-07-19 17:40:50 +00:00
v3-lite-bot 41f4879f1e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@be5c4ac -> sha256:3317b53da1a9 2026-07-19 17:22:11 +00:00
v3-lite-bot 1f047e7969 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d6c91d1 -> sha256:9baa0e7a83c0 2026-07-19 17:11:08 +00:00
knowledge-publisher[bot] bd7751def1 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@be5c4ac666 2026-07-19 17:05:36 +00:00
v3-lite-bot 29126e1528 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@15cd3fd -> sha256:bb873674e9a3 2026-07-19 16:48:53 +00:00
v3-lite-bot ff94196547 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d4c12fb -> sha256:f38f8eba7179 2026-07-19 16:22:05 +00:00
knowledge-publisher[bot] 16f3da789a feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@d4c12fb610 2026-07-19 16:11:20 +00:00
v3-lite-bot 0d5d595801 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c00bcd1 -> sha256:7bb41a5090a6 2026-07-19 16:06:37 +00:00
knowledge-publisher[bot] 8e9666b1f5 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@c00bcd1c2e 2026-07-19 15:57:04 +00:00
v3-lite-bot f826671af2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4a19710 -> sha256:fc6f2256373e 2026-07-19 15:48:05 +00:00
knowledge-publisher[bot] c986f3690d feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@4a19710f99 2026-07-19 15:37:01 +00:00
v3-lite-bot 87f42bb267 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b1d6ecb -> sha256:999b69d5c5c0 2026-07-19 15:27:19 +00:00
knowledge-publisher[bot] d06f1a8660 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@b1d6ecb2a0 2026-07-19 15:17:03 +00:00
v3-lite-bot bd3488243a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7ec4aaf -> sha256:ba2e1cfb0a79 2026-07-19 14:54:34 +00:00
knowledge-publisher[bot] 6801d61662 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@7ec4aaf0f1 2026-07-19 14:43:40 +00:00
v3-lite-bot 1873be591d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@816a294 -> sha256:4bb73b243013 2026-07-19 14:04:51 +00:00
v3-lite-bot a4f2fcc330 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@06f10ba -> sha256:490e383f4c3f 2026-07-19 13:37:47 +00:00
BrunoGao 86fdcda98b fix(health): preserve underscore in RLS verification (#311) 2026-07-19 21:07:14 +08:00
BrunoGao e69c2bedbf fix(gitops): trigger health schema hooks on bundle changes (#310) 2026-07-19 21:03:20 +08:00
BrunoGao 4bf87e5ba3 fix(health): deploy platform-default RLS policy (#309) 2026-07-19 21:00:43 +08:00
v3-lite-bot 29bed96c93 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@94121fa -> sha256:8c333b325b28 2026-07-19 12:52:40 +00:00
knowledge-publisher[bot] 3370f632d5 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@f9f1cc8f7c 2026-07-19 12:52:31 +00:00
BrunoGao 44667c9ac9 fix(incident): start relay through ConfigMap symlink (#308) 2026-07-19 20:51:56 +08:00
BrunoGao 43fe777b11 feat(incident): enforce alert acknowledgement SLA (#307) 2026-07-19 20:48:46 +08:00
v3-lite-bot cba46e8bfe chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@77259c3 -> sha256:f80460b6c043 2026-07-19 12:26:19 +00:00
v3-lite-bot f0a24211c8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@552abc1 -> sha256:0ab3c5eb3755 2026-07-19 12:15:32 +00:00
knowledge-publisher[bot] b232dc926d feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@77259c3b04 2026-07-19 12:12:09 +00:00
v3-lite-bot 0845eadcca chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1436866 -> sha256:89a88a8a1437 2026-07-19 12:04:00 +00:00
v3-lite-bot d0a2700cd1 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4425fc6 -> sha256:8c023f25b8d8 2026-07-19 11:38:19 +00:00
knowledge-publisher[bot] 4d0ce22a45 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@6326b971f3 2026-07-19 11:01:27 +00:00
BrunoGao a6533accc0 Merge pull request #306 from lingjingwanxiang/agent/storage-capacity-alerts-375
feat(health): deploy storage capacity alerts
2026-07-19 19:00:50 +08:00
v3-lite-bot 06b81dca4e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@86947de -> sha256:4d1978bd77bf 2026-07-19 10:59:53 +00:00
bruno a35165bc34 feat(health): deploy storage capacity alerts 2026-07-19 18:54:37 +08:00
v3-lite-bot 5d070854c8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@54e52ae -> sha256:cff98558f6db 2026-07-19 10:31:16 +00:00
BrunoGao cb4acc8686 Merge pull request #305 from lingjingwanxiang/agent/health-collection-policy-schema-373
feat(health): deploy collection policy schema
2026-07-19 17:49:30 +08:00
v3-lite-bot 1baaa020d2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0ea5874 -> sha256:f1b5009ca3cc 2026-07-19 09:47:42 +00:00
bruno 301c131ff1 feat(health): deploy collection policy schema 2026-07-19 17:40:29 +08:00
knowledge-publisher[bot] e9e6c93249 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@0ea587470e 2026-07-19 09:38:17 +00:00
BrunoGao 5b845d9c16 Merge pull request #304 from lingjingwanxiang/agent/health-shadow-init-fix-349
fix(health): make shadow datastore hooks idempotent
2026-07-19 17:32:59 +08:00
bruno 4595681367 fix(health): make shadow datastore hooks idempotent 2026-07-19 17:32:10 +08:00
BrunoGao a7113a98a4 Merge pull request #303 from lingjingwanxiang/agent/health-shadow-canary-349
feat(health): deploy governed shadow and score canaries (#349, #355)
2026-07-19 16:44:39 +08:00
bruno 251d1d2184 feat(health): canary realtime score evidence (#355) 2026-07-19 16:42:20 +08:00
bruno 20e205f7d6 feat(health): deploy governed shadow canary (#349) 2026-07-19 16:42:20 +08:00
v3-lite-bot 1485bc8c92 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d530cd5 -> sha256:e9a770bcc393 2026-07-19 08:36:03 +00:00
knowledge-publisher[bot] c75bdbc87c feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@d530cd5415 2026-07-19 08:25:39 +00:00
v3-lite-bot ff1c4d7cb2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@98ded89 -> sha256:ac28cc2fc32c 2026-07-19 08:11:53 +00:00
knowledge-publisher[bot] f59174c38c feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@36ed22b052 2026-07-19 07:39:02 +00:00
v3-lite-bot 2fb2998a73 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@70c04b3 -> sha256:37547c758c0e 2026-07-19 06:47:40 +00:00
knowledge-publisher[bot] 93ffb4da90 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@70c04b3055 2026-07-19 06:38:08 +00:00
BrunoGao a66007da4e fix(ljwx-health): align MinIO runtime credentials (#302) 2026-07-19 14:17:57 +08:00
BrunoGao 0d18d908d2 fix(ljwx-health): pin pre-sync DB identity (#301) 2026-07-19 14:00:42 +08:00
BrunoGao c43fabbcf5 fix(ljwx-health): enforce restricted runtime DB role (#300) 2026-07-19 13:57:33 +08:00
v3-lite-bot 05e1c4bd6b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e5042a1 -> sha256:021bb31f00de 2026-07-19 05:55:49 +00:00
v3-lite-bot 071e23d92e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0de2cfe -> sha256:ee47dfaf7109 2026-07-19 05:31:25 +00:00
v3-lite-bot 49fb9f8ce0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@6a4794a -> sha256:9f2ec6a2b41a 2026-07-19 05:21:01 +00:00
knowledge-publisher[bot] 3054dc6b4b feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@6a4794a3e6 2026-07-19 05:12:21 +00:00
v3-lite-bot 8462b9e593 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4c3fec7 -> sha256:9b5a39a52b4f 2026-07-19 05:08:06 +00:00
v3-lite-bot c74ed25a4f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@340a6fe -> sha256:0242876131c2 2026-07-19 04:52:22 +00:00
BrunoGao e0448b0683 feat(health): add bigscreen trial observability (#299) 2026-07-19 12:23:24 +08:00
v3-lite-bot 53beb96081 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@49f9d50 -> sha256:1bf4fe7f9778 2026-07-19 04:20:53 +00:00
v3-lite-bot d2d214eb0c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7fad30d -> sha256:43232fc72e62 2026-07-19 04:08:07 +00:00
knowledge-publisher[bot] aad2025d6c feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@0215af62e2 2026-07-19 04:05:25 +00:00
knowledge-publisher[bot] f1aa4e70ec feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@7fad30d0b6 2026-07-19 03:58:31 +00:00
knowledge-publisher[bot] 9bc70f15e2 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@e67818ab88 2026-07-19 03:16:02 +00:00
BrunoGao dd8599e1ca Merge pull request #298 from lingjingwanxiang/agent/bigscreen-restore-allowlist-172
feat(health): restore bigscreen tenant-1 canary
2026-07-19 11:08:49 +08:00
bruno 979075b4b5 feat(health): restore bigscreen tenant-1 canary (#172) 2026-07-19 11:08:06 +08:00
BrunoGao 3371005e59 Merge pull request #297 from lingjingwanxiang/agent/bigscreen-rollback-rehearsal-172
test(health): rehearse bigscreen legacy rollback
2026-07-19 11:06:20 +08:00
bruno 2982fd079e test(health): rehearse bigscreen legacy rollback (#172) 2026-07-19 11:05:29 +08:00
BrunoGao cf64406ca4 Merge pull request #296 from lingjingwanxiang/agent/bigscreen-allowlist-172
feat(health): allowlist tenant 1 for bigscreen cutover
2026-07-19 11:02:56 +08:00
bruno eab654612f feat(health): allowlist tenant 1 for bigscreen cutover (#172) 2026-07-19 11:02:06 +08:00
v3-lite-bot 264d879498 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4ae27e7 -> sha256:4b90ac8582e6 2026-07-19 02:57:51 +00:00
BrunoGao 53135d713f Merge pull request #295 from lingjingwanxiang/agent/restore-bigscreen-resource-172
fix(health): restore 5225 compatibility workload
2026-07-19 10:52:22 +08:00
bruno 569e1458b4 fix(health): restore 5225 compatibility workload (#172) 2026-07-19 10:51:25 +08:00
knowledge-publisher[bot] 7c9b497d74 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@88b422dace 2026-07-19 02:32:24 +00:00
BrunoGao 622ab4dfd1 Merge pull request #294 from lingjingwanxiang/agent/bigscreen-cutover-172-gitops
feat(health): configure safe bigscreen cutover defaults
2026-07-19 10:31:01 +08:00
bruno 983f249652 feat(health): configure safe bigscreen cutover defaults
Refs https://github.com/lingjingwanxiang/ljwx-health/issues/172
2026-07-19 10:29:39 +08:00
v3-lite-bot 28c0793c54 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5ac50a2 -> sha256:653c2eb6e924 2026-07-19 02:13:30 +00:00
v3-lite-bot e1fe3dd19d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ca00b48 -> sha256:0799c733ca3b 2026-07-19 00:45:48 +00:00
knowledge-publisher[bot] 4cc3b65952 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@cb92f1666b 2026-07-19 00:36:59 +00:00
v3-lite-bot 85ff7dd292 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@6df36cf -> sha256:5caeff206493 2026-07-19 00:12:58 +00:00
v3-lite-bot dbf370d1f3 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@aa95ca5 -> sha256:d8952d6cd8fe 2026-07-18 23:43:33 +00:00
knowledge-publisher[bot] fb28799d81 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@aa95ca523a 2026-07-18 23:38:48 +00:00
v3-lite-bot bcfc809377 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a08092d -> sha256:c498e157f77e 2026-07-18 23:14:26 +00:00
v3-lite-bot 75b65ae961 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@bbc8669 -> sha256:c7d7f05cd6fe 2026-07-18 22:35:26 +00:00
knowledge-publisher[bot] b080c251d1 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@bbc8669fa5 2026-07-18 22:30:19 +00:00
knowledge-publisher[bot] 5f13a4b0b3 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@eed08f8e35 2026-07-13 15:49:29 +00:00
v3-lite-bot 7b99e3c357 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@704d729 -> sha256:4be1fca6af10 2026-07-13 15:09:38 +00:00
knowledge-publisher[bot] f379cd85a3 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@704d729003 2026-07-13 15:04:53 +00:00
bruno 7efc01cbab feat(zhishiku): publish financial HTTPS ingress 2026-07-13 19:26:13 +08:00
v3-lite-bot 197af76701 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@bce2b85 -> sha256:7eaba19df774 2026-07-13 11:25:46 +00:00
bruno 4edb33243d fix(zhishiku): apply synced Harbor digest 2026-07-13 19:08:35 +08:00
deploy-controller-v2[bot] c23ae3b814 chore(deploy): apply release-state resolutions [skip ci] 2026-07-13 10:33:05 +00:00
v3-lite-bot cb0f277304 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3d7ca59 -> sha256:c29112c99bcf 2026-07-13 10:31:34 +00:00
bruno c596bfde59 Revert "fix: promote dev runtime images to Harbor"
This reverts commit efab683385.
2026-07-13 18:26:23 +08:00
bruno efab683385 fix: promote dev runtime images to Harbor 2026-07-13 18:24:36 +08:00
deploy-controller-v2[bot] c16365e1af chore(deploy): apply release-state resolutions [skip ci] 2026-07-13 10:21:08 +00:00
v3-lite-bot b8e6849d93 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@daef6bc -> sha256:7b5acdb436bf 2026-07-13 10:04:14 +00:00
v3-lite-bot 958f49b93c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@22ce7b3 -> sha256:0a9f2d559481 2026-07-13 09:58:10 +00:00
bruno 57fec14864 feat: onboard Simple IMA on local k3s 2026-07-13 17:56:26 +08:00
v3-lite-bot 81e6dd9c7a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3074a05 -> sha256:415bdcdf1684 2026-07-13 09:48:29 +00:00
knowledge-publisher[bot] 56e6ff710a feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@988e5b1151 2026-07-13 09:07:37 +00:00
v3-lite-bot 65fdb50b59 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@dad943c -> sha256:7e5c931cf6a9 2026-07-12 13:56:38 +00:00
BrunoGao a35a5f347e fix(cert-manager): rotate Cloudflare DNS credentials (#293)
* fix(cert-manager): rotate Cloudflare DNS credentials

* docs(cert-manager): clarify account token verification
2026-07-12 20:54:25 +08:00
BrunoGao 67b9673ee1 fix(health): rewrite public API gateway prefix (#292)
* fix(health): rewrite public API gateway prefix

* fix(ci): use cross-repo token for issue validation
2026-07-12 18:46:39 +08:00
BrunoGao f8a7d98557 ci(health): enforce operations issue contracts (#290)
* ci(health): enforce operations issue contracts

* fix(ci): authenticate pinned ops gate fetch

* fix(ci): vendor tested operations gate
2026-07-12 18:31:38 +08:00
BrunoGao ffc13f4287 fix(health): suppress Sa-Token configuration logger (#291) 2026-07-12 18:30:33 +08:00
v3-lite-bot d25c178938 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f3696e7 -> sha256:19101006eec2 2026-07-12 10:20:25 +00:00
knowledge-publisher[bot] 453e102e71 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@bd062388ed 2026-07-12 10:06:42 +00:00
v3-lite-bot f7e7c5617b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4d61f12 -> sha256:253234d9f007 2026-07-12 10:05:25 +00:00
BrunoGao e13ef26120 fix(health): inject and rotate Sa-Token signing secret (#289) 2026-07-12 17:57:23 +08:00
v3-lite-bot 55f7b0f85f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b929d37 -> sha256:6cd7a84b94c0 2026-07-12 09:04:39 +00:00
knowledge-publisher[bot] 667d25383c feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@b929d3752b 2026-07-12 09:00:03 +00:00
v3-lite-bot 78b07c4b2a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@555da27 -> sha256:3635ed933f07 2026-07-12 06:16:53 +00:00
knowledge-publisher[bot] 20345ef7d6 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@555da27152 2026-07-12 06:11:53 +00:00
knowledge-publisher[bot] bddc9e8817 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@4dc76a9830 2026-07-12 04:44:51 +00:00
knowledge-publisher[bot] 4f8b1b735a feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@d529647719 2026-07-12 00:29:44 +00:00
v3-lite-bot ea2b70c317 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ac2dca3 -> sha256:4836c83cf224 2026-07-11 23:33:15 +00:00
knowledge-publisher[bot] 6297985a6d feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@ac2dca3fd1 2026-07-11 23:27:39 +00:00
v3-lite-bot cd88fd9b3d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e33d8ca -> sha256:e71f0a4db546 2026-07-11 10:50:33 +00:00
knowledge-publisher[bot] 0c7f6fc445 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@e33d8caa74 2026-07-11 10:45:33 +00:00
knowledge-publisher[bot] 014e4ee2a4 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@96906738b4 2026-07-11 09:40:02 +00:00
BrunoGao e0a861b20a fix(ljwx-health): order schema config before hook (#288) 2026-07-11 08:48:39 +08:00
BrunoGao 1cfa358feb fix(ljwx-health): execute Epic 360 schema rollout (#287) 2026-07-11 08:46:36 +08:00
v3-lite-bot 5ab2dafe6d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c7af2fd -> sha256:c8c0d7e21d75 2026-07-11 00:38:14 +00:00
knowledge-publisher[bot] 00ee1d96cd feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@c7af2fd9a1 2026-07-11 00:33:52 +00:00
BrunoGao d325c6da66 feat(ljwx-health): add message chain alert rules (#286) 2026-07-11 08:26:57 +08:00
knowledge-publisher[bot] 0198e586d8 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@cfca76fba9 2026-07-10 15:14:01 +00:00
v3-lite-bot 4f8fa6eb53 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c845ec1 -> sha256:5d7ae678c96e 2026-07-10 15:11:20 +00:00
knowledge-publisher[bot] 3188d811e3 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@c845ec149b 2026-07-10 15:06:11 +00:00
knowledge-publisher[bot] fba81a04b9 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@8111f414b4 2026-07-10 15:00:33 +00:00
BrunoGao 477eea302f feat(ops): close LJWX Health alert edge and PITR loops (#285)
Add the Feishu alert relay, dedicated in-cluster Cloudflare Tunnel, pgBackRest WAL/PITR operations, supporting Argo applications, and reproducible production drills.
2026-07-10 22:59:40 +08:00
knowledge-publisher[bot] ec09239bed feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@57134e651e 2026-07-10 13:58:38 +00:00
v3-lite-bot 5584caa12f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c3013b8 -> sha256:b84149526321 2026-07-10 13:41:18 +00:00
knowledge-publisher[bot] 79d1d47daa feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@c3013b8a68 2026-07-10 13:36:13 +00:00
BrunoGao f77e477588 fix(ljwx-health): redact health dependency details (#284) 2026-07-10 21:17:20 +08:00
v3-lite-bot 74385cf494 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@08930c9 -> sha256:a8da6089fa24 2026-07-10 13:03:25 +00:00
knowledge-publisher[bot] 5664c8925d feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@08930c940a 2026-07-10 12:58:15 +00:00
BrunoGao 90b5134afa rollback(ljwx-health): restore Boot and fix OTLP transport (#283)
* fix(ljwx-health): use OTLP HTTP transport

* rollback(ljwx-health): restore healthy boot digest
2026-07-10 20:53:26 +08:00
v3-lite-bot 4dbbc47abf chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4987ca2 -> sha256:3990b9ae932e 2026-07-10 12:43:10 +00:00
BrunoGao c98c6532d3 fix(ljwx-health): align TLS with Cloudflare edge (#282) 2026-07-10 20:36:40 +08:00
BrunoGao 2713658ad4 feat(ljwx-health): production operations baseline (#281) 2026-07-10 20:31:20 +08:00
bruno 647bc48917 fix: align website container port with runtime 2026-07-10 20:02:17 +08:00
bruno 1a15c14242 fix: allow cert-manager solver in openclaw namespace 2026-07-10 19:58:46 +08:00
bruno 42f538fae2 fix: restore unhealthy k3s workloads 2026-07-10 19:56:37 +08:00
v3-lite-bot 63fe0ae6f5 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3f90aef -> sha256:57ddc468fa72 2026-07-09 18:38:38 +00:00
v3-lite-bot 14734e0ced chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5865918 -> sha256:7047a01b2d7f 2026-07-09 18:08:40 +00:00
v3-lite-bot 4d6bf43df3 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e10aff1 -> sha256:aa07f8c9087b 2026-07-09 17:51:41 +00:00
v3-lite-bot 01bc01e85f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ce148dc -> sha256:ccb683174d24 2026-07-09 17:30:29 +00:00
v3-lite-bot f193622eff chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1d48fc6 -> sha256:c2c8e9062118 2026-07-09 17:07:58 +00:00
v3-lite-bot 803e0bb93a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7d4797e -> sha256:ddc2a31bb5e4 2026-07-09 16:50:23 +00:00
v3-lite-bot ac6d862198 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@15a332c -> sha256:b46e4481b672 2026-07-09 16:36:35 +00:00
v3-lite-bot f94df2e113 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d963da1 -> sha256:b0c56bfb6f76 2026-07-09 16:30:29 +00:00
v3-lite-bot 9b1d710bfe chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@cd07d14 -> sha256:1b619e619ee3 2026-07-09 16:14:45 +00:00
v3-lite-bot ad3267d39d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@070d50d -> sha256:615c3da34e84 2026-07-09 16:09:27 +00:00
v3-lite-bot d140c2893e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@62bc21b -> sha256:5a39d6884c2f 2026-07-09 15:55:36 +00:00
bruno 82efd19e2f chore: expose local llm default envs 2026-07-09 23:18:18 +08:00
v3-lite-bot e5ff221cf9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b1ff8be -> sha256:e43cf75511ae 2026-07-09 01:00:23 +00:00
v3-lite-bot 18720fae0b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@615e037 -> sha256:fd07ab844bca 2026-07-08 17:04:55 +00:00
v3-lite-bot 0e8f1e8ecd chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@2038544 -> sha256:1b13d47c66b7 2026-07-08 16:43:28 +00:00
v3-lite-bot 716df72f68 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@25b207a -> sha256:67963265ca71 2026-07-08 16:25:08 +00:00
v3-lite-bot 910fa32a30 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@91a2f39 -> sha256:1ec8c573a367 2026-07-08 16:12:55 +00:00
knowledge-publisher[bot] 49b2d49484 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@91a2f393f9 2026-07-08 16:07:41 +00:00
v3-lite-bot 7acc06e826 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@596ebec -> sha256:75599b18774a 2026-07-08 15:48:52 +00:00
v3-lite-bot d45b41bea9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a289c0c -> sha256:b08c665830a5 2026-07-08 14:17:58 +00:00
v3-lite-bot cd539314c9 chore(omni-stock): deploy lingjingwanxiang/omni-stock@644248f -> sha256:dbf8c3e5805e 2026-07-08 13:48:45 +00:00
v3-lite-bot e4467d1167 chore(omni-stock): deploy lingjingwanxiang/omni-stock@644248f -> sha256:c1e841399120 2026-07-08 13:46:31 +00:00
v3-lite-bot ed628eda6b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@244837d -> sha256:19fb96ec8c65 2026-07-08 13:41:56 +00:00
v3-lite-bot 08b3c9d5c2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0ede16e -> sha256:c48b36c4c062 2026-07-08 13:20:14 +00:00
v3-lite-bot f87172fedf chore(omni-stock): deploy lingjingwanxiang/omni-stock@fad46a3 -> sha256:988f30a4eb36 2026-07-08 09:40:43 +00:00
v3-lite-bot 8f9aaa8838 chore(omni-stock): deploy lingjingwanxiang/omni-stock@fad46a3 -> sha256:df38f53ac6d1 2026-07-08 09:38:38 +00:00
v3-lite-bot 547f4045f9 chore(ljwx-shiti): deploy lingjingwanxiang/ljwx-shiti@3fee3a3 -> sha256:61e1f4abb90e 2026-07-08 09:35:29 +00:00
v3-lite-bot 9a19e06fad chore(omni-stock): deploy lingjingwanxiang/omni-stock@c1466e2 -> sha256:8d1cd7607c95 2026-07-08 09:33:32 +00:00
v3-lite-bot 92491e5f12 chore(omni-stock): deploy lingjingwanxiang/omni-stock@9028ba6 -> sha256:9c58790e08ee 2026-07-08 09:25:55 +00:00
v3-lite-bot a206e576b7 chore(omni-stock): deploy lingjingwanxiang/omni-stock@5e0fbe3 -> sha256:05ee2a01bf11 2026-07-08 09:19:25 +00:00
v3-lite-bot 6fe6741a76 chore(omni-stock): deploy lingjingwanxiang/omni-stock@9ed1be4 -> sha256:78dbfe23c7fc 2026-07-08 09:11:51 +00:00
v3-lite-bot ce881e8a17 chore(omni-stock): deploy lingjingwanxiang/omni-stock@89883c1 -> sha256:4cb26ee99922 2026-07-08 09:04:09 +00:00
v3-lite-bot 7c2c5ea5f4 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@dac0a4d -> sha256:cd582b354302 2026-07-07 17:58:10 +00:00
v3-lite-bot 1267ba9381 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@32c934a -> sha256:002232923a5b 2026-07-06 07:52:29 +00:00
v3-lite-bot 34b7e75541 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@50c6650 -> sha256:2996f31db791 2026-07-06 07:47:12 +00:00
v3-lite-bot 647815a2c4 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@284a600 -> sha256:34bcac4d0e29 2026-07-06 01:08:13 +00:00
v3-lite-bot 60049b1e87 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a8ddde7 -> sha256:17e370e47381 2026-07-06 01:02:53 +00:00
v3-lite-bot 61912ab1b7 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@8b11dd1 -> sha256:42248081df9b 2026-07-06 00:57:26 +00:00
v3-lite-bot cae2d8499e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@32f7dbc -> sha256:4f654e207fab 2026-07-06 00:52:12 +00:00
v3-lite-bot e2c4840f71 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@081859b -> sha256:8ac7a148dd0b 2026-07-06 00:46:39 +00:00
v3-lite-bot 54a451188c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@66dbe2d -> sha256:f0bcc2487560 2026-07-06 00:40:33 +00:00
v3-lite-bot 6fff19d18c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@abb33e2 -> sha256:d127f5d6d979 2026-07-06 00:29:27 +00:00
v3-lite-bot a6db1a33f9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@363856e -> sha256:b451454ffd34 2026-07-05 15:25:56 +00:00
v3-lite-bot ba968eaf69 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0f9e415 -> sha256:98173d1f7827 2026-07-05 14:54:57 +00:00
v3-lite-bot c105c23ebd chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9f8c8f3 -> sha256:41a7d475551b 2026-07-05 12:01:12 +00:00
v3-lite-bot 0d3c37bff2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1a86c5e -> sha256:3271e4e0bf19 2026-07-05 08:00:47 +00:00
v3-lite-bot af4e1d6e83 chore(omni-website): deploy lingjingwanxiang/omni-website@d4707d1 -> sha256:593f612fa92c 2026-07-01 11:11:49 +00:00
v3-lite-bot fcc42ba95e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@434d145 -> sha256:f25e5b37b4b5 2026-06-21 12:35:16 +00:00
v3-lite-bot c9c737ed3a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f7e5ad7 -> sha256:ccd20449332e 2026-06-20 11:59:07 +00:00
v3-lite-bot dab729dbaa chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0e38945 -> sha256:4e455f69343c 2026-06-19 13:38:19 +00:00
v3-lite-bot 487e8f7a3e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3438802 -> sha256:5075be0a4292 2026-06-19 13:16:17 +00:00
v3-lite-bot 7a5fba66ff chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0b44395 -> sha256:c12924bbc2c4 2026-06-19 08:31:55 +00:00
v3-lite-bot 661c0e1603 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@966f504 -> sha256:bb8f79919a5c 2026-06-19 07:50:42 +00:00
v3-lite-bot 3205171911 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a89594d -> sha256:af1223819871 2026-06-19 07:36:01 +00:00
v3-lite-bot 6e45ac97c3 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f0c9080 -> sha256:307f6dd715e4 2026-06-19 05:02:04 +00:00
v3-lite-bot 86f3a14e6b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@2c10823 -> sha256:e9b1077e0f2b 2026-06-19 04:24:42 +00:00
knowledge-publisher[bot] 258bba7dc7 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@6e3b29b861 2026-06-17 07:06:38 +00:00
v3-lite-bot 984d19d881 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@085d815 -> sha256:88fc9db520e2 2026-06-11 11:41:37 +00:00
v3-lite-bot 2fa318499d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@39548d5 -> sha256:1107e21728d9 2026-06-11 11:09:58 +00:00
knowledge-publisher[bot] 8582b417c4 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@39548d5816 2026-06-11 10:50:28 +00:00
v3-lite-bot aa7f738f37 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@6dba766 -> sha256:c78321792b88 2026-06-10 15:04:45 +00:00
bruno a910455aec fix(ljwx-health): route dashboard to admin 2026-06-10 22:54:24 +08:00
bruno 4080b5bfd9 fix(ljwx-health): wire bigscreen to boot service 2026-06-10 22:44:01 +08:00
bruno 821a0341fc fix(ljwx-health): point bigscreen probes at API health 2026-06-10 22:40:59 +08:00
bruno 77814f2885 fix(ljwx-health): disable runtime flyway on k3s 2026-06-10 22:34:32 +08:00
v3-lite-bot 666b6588b3 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@861bb05 -> sha256:ff6786751ad0 2026-06-10 14:28:11 +00:00
v3-lite-bot ec57834cf6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@8361916 -> sha256:905ee4f04459 2026-06-08 15:47:59 +00:00
v3-lite-bot 4bf4679d98 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c5d2625 -> sha256:35f2850e0530 2026-06-08 04:48:50 +00:00
knowledge-publisher[bot] 43891a6adb feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@c5d2625690 2026-06-08 04:31:08 +00:00
v3-lite-bot 41f886ddff chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b5bc86f -> sha256:5ef23ac4eebd 2026-06-02 01:06:50 +00:00
v3-lite-bot 994ac449b8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@bc2461c -> sha256:a86ba5c48112 2026-06-01 15:31:23 +00:00
v3-lite-bot 42e15e3b02 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f793c96 -> sha256:7f6ec5b25f27 2026-06-01 15:20:07 +00:00
knowledge-publisher[bot] 29fef27eb6 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@f793c9631e 2026-06-01 15:12:29 +00:00
v3-lite-bot 8471dacc7d chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@a0dde15 -> sha256:f41831482a3c 2026-05-24 07:54:31 +00:00
knowledge-publisher[bot] e1638d2b3f feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@19fafd0228 2026-05-24 05:59:46 +00:00
knowledge-publisher[bot] abddd47c6f feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@aaa7fa5a74 2026-05-24 03:56:09 +00:00
knowledge-publisher[bot] bbca9122d0 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@5abff5b753 2026-05-24 03:24:51 +00:00
bruno d52a868954 ci(actions): upgrade docker workflow actions 2026-05-24 10:42:43 +08:00
bruno 6e77973404 ci(actions): upgrade workflow actions for node24 2026-05-24 10:40:37 +08:00
knowledge-publisher[bot] 008ff68f9e feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@a2ca9e3101 2026-05-24 02:31:03 +00:00
v3-lite-bot f234d1c156 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@68b6e9d -> sha256:2391a0d89ba7 2026-05-21 09:34:29 +00:00
v3-lite-bot 935c4b846b chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@fadeb9c -> sha256:a6c09d373072 2026-05-20 19:09:01 +00:00
v3-lite-bot 333ad39b05 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@de13300 -> sha256:1438d808a803 2026-05-20 18:42:53 +00:00
v3-lite-bot 01dfeab9bf chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@e797bd9 -> sha256:29614c6186d5 2026-05-20 17:38:26 +00:00
knowledge-publisher[bot] 92dcccbe66 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@0efc168fc1 2026-05-20 17:17:08 +00:00
v3-lite-bot 52cd098e29 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@f63b691 -> sha256:32bd4cc3d8a6 2026-05-20 16:58:10 +00:00
v3-lite-bot 403e2367a6 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@0219a83 -> sha256:4c43edaa40f3 2026-05-20 16:34:15 +00:00
knowledge-publisher[bot] 65d04830fb feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@f63b691ccd 2026-05-20 16:22:19 +00:00
v3-lite-bot 2ea831135c chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@0f1b39d -> sha256:3e79c4c9e126 2026-05-20 16:07:36 +00:00
v3-lite-bot fb0bfbce69 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@647eea2 -> sha256:fb26130734d3 2026-05-19 18:32:58 +00:00
v3-lite-bot ff94ade43e chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@e74b06c -> sha256:49a604c07896 2026-05-19 12:08:11 +00:00
v3-lite-bot 8984e05b13 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@053add6 -> sha256:bc733bd0d726 2026-05-19 11:44:58 +00:00
v3-lite-bot 51ede02a93 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@1fedbcb -> sha256:6958881deb00 2026-05-19 02:23:41 +00:00
v3-lite-bot b81ce74681 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@ea56d0d -> sha256:f1b75e2afe64 2026-05-19 02:00:21 +00:00
v3-lite-bot c15a2484d5 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@2fbd4e1 -> sha256:31b38d7d3643 2026-05-19 01:34:38 +00:00
v3-lite-bot f30781a0c0 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@822c06a -> sha256:6f716900fc21 2026-05-18 05:07:10 +00:00
v3-lite-bot 45159b8f54 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@e97c11d -> sha256:96a082ca0255 2026-05-18 04:44:26 +00:00
knowledge-publisher[bot] 3d1ed9f93f feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@c3bd937b0e 2026-05-18 04:42:30 +00:00
knowledge-publisher[bot] 07abbc81dd feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@f56ffc81ce 2026-05-18 04:25:21 +00:00
v3-lite-bot 9ef4d11885 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@9d5098c -> sha256:cb1eae8f04e2 2026-05-18 04:20:34 +00:00
v3-lite-bot 37717d53cf chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@dec26ce -> sha256:11a4410fdc34 2026-05-18 03:35:26 +00:00
v3-lite-bot f36277a8d0 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@332b730 -> sha256:f3dd1a009646 2026-05-18 03:08:18 +00:00
knowledge-publisher[bot] 35df30fdeb feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@a9b6bb85dc 2026-05-18 02:03:29 +00:00
knowledge-publisher[bot] 027d306c05 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@7fc5f5e964 2026-05-18 01:52:35 +00:00
knowledge-publisher[bot] 94cfd30aee feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@e0e64b07c0 2026-05-18 01:39:26 +00:00
knowledge-publisher[bot] ebd5b89ecd feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@50f9cc5ccc 2026-05-18 01:10:23 +00:00
knowledge-publisher[bot] 3eab8bdc1f feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@630731a3eb 2026-05-18 00:51:50 +00:00
knowledge-publisher[bot] 654e28a17f feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@25aec079f3 2026-05-18 00:37:34 +00:00
knowledge-publisher[bot] 087cd93d05 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@a1088f3b33 2026-05-18 00:22:11 +00:00
brunoandClaude Opus 4.7 e400e98db4 feat(knowledge): W3.2 line-range chunks + W4.5 drift check
W3.2 (source line ranges per chunk):
- common.MarkdownChunk dataclass + chunk_markdown_with_lines() tracks
  1-indexed source line ranges as it groups paragraphs into chunks.
  Old chunk_markdown() preserved as a thin wrapper for callers that
  don't need line ranges.
- ingest_artifact.build_chunks_for_document now emits
  source_start_line / source_end_line on every chunk.
- knowledge-chunk schema accepts the two new fields (nullable for
  backward compat with pre-W3.2 chunks; required for new ones).
- context_pack.PackSection carries the same fields; module-map
  sections record start=1 / end=line_count of the full file. Manifest
  output and pack.md citation grammar are unchanged.
- validate_citations.verify() for kind=file_line now searches the
  pack for sections whose source_path matches AND whose line range
  overlaps the citation. On hit, inherits that section's tier
  (typically ssot for module.yml/ADR sections). On no overlap,
  falls back to the existing weak verification (bounds-only).

Smoke (against ljwx-platform with module-map):
- spec/modules/license/module.yml:L1-L14 -> tier=ssot, severity=block
- docs/adr/006-security-hardening.md:L40-L60 -> tier=ssot, severity=block
- :L200-L300 (out of bounds) -> unverified, info
- nonexistent/path.md -> unverified, info

W4.5 (Dify drift check):
- new scripts/knowledge/dify_drift_check.py: read-only sweep that for
  each published record verifies (1) the dataset still exists with the
  recorded dataset_id, (2) the document still exists with the recorded
  remote_document_id, (3) the document name on Dify matches.
- Emits per-record + per-repo JSON report; exits non-zero with
  --fail-on-drift for CI.
- Smoke against ljwx-platform: 0 drift in 10-record sample.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-18 00:12:50 +08:00
knowledge-publisher[bot] 1ebd0a60dc feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@5b7392684a 2026-05-17 15:58:39 +00:00
v3-lite-bot 19d018aba5 chore(ljwx-bookstore): deploy lingjingwanxiang/ljwx-bookstore@422d078 -> sha256:4e74338de6db 2026-05-17 15:39:06 +00:00
v3-lite-bot 1cb28579b0 chore(ljwx-shiti): deploy lingjingwanxiang/ljwx-shiti@c6acb39 -> sha256:b9ade0322f75 2026-05-17 14:36:58 +00:00
v3-lite-bot 573b172e2f chore(omni-website): deploy lingjingwanxiang/omni-website@1acaa1f -> sha256:f47715b5dec3 2026-05-17 14:32:20 +00:00
brunoandClaude Opus 4.7 711d9ee6f1 feat(knowledge): onboard 5 business repos + dify_client retry
Onboards 5 business repos (omni-website 1 doc, ljwx-shiti 5, ljwx-greenway 4,
zhihu-engine-new 8, ljwx-bookstore 50 after W2 split) and adds retry-with-backoff
to dify_client._request so transient Dify 5xx / RemoteDisconnected don't abort
multi-hundred-document ingest runs.

Source registry: new artifact_sources entries for the 5 repos; ljwx-bookstore
ids expanded to match the new W2 sources.yaml.

dify_client retries on HTTP 502/503/504, RemoteDisconnected, ConnectionError,
TimeoutError. Up to 4 attempts with backoff 1s/3s/7s. Per-request timeout
raised 15s -> 30s for bulk-ingest survival.

W8 CLAUDE.md hooks added in each business repo via separate per-repo commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 22:10:40 +08:00
v3-lite-bot ce242e7f70 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@8dbd3c6 -> sha256:0306cdb22c98 2026-05-17 14:08:18 +00:00
v3-lite-bot c9d0a97ab4 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@c92021f -> sha256:f144aaa2d99f 2026-05-17 12:54:12 +00:00
v3-lite-bot eb92f3b92a chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@ce27071 -> sha256:7cb01182472e 2026-05-17 12:28:22 +00:00
brunoandClaude Opus 4.7 15fa67353e feat(knowledge): module-map schema + validator + Dify query cap fix (W3.5)
W3.5 implementation: enable Codex/Claude to resolve changed-files in a
PR diff to the spec module(s) and ADRs they belong to, before falling
back to text retrieval.

- platform/contracts/knowledge-module-map.schema.yaml: JSON Schema
  defining the new knowledge.module-map.yaml file business repos add at
  their root. Required fields: schema_version, service, modules.
  Each module entry has paths (fnmatch globs), excluded_paths, spec
  pointer, and adrs list. Optional fallback_adrs at the top level
  covers cross-cutting PRs.
- scripts/knowledge/validate_module_map.py: validates the yaml against
  schema + checks every spec/adrs path exists in the working tree +
  warns on duplicate path patterns + warns on dead module entries.
  CLI takes --module-map / --repo-root / --deploy-root and exits 1 on
  any error.
- scripts/knowledge/context_pack.py: cap the Dify retrieve query at
  240 chars (Dify rejects >250 with HTTP 400 invalid_param). Also
  switched from full paths to basenames in the query string for
  higher retrieval signal density.

W7 baseline on 4 recent ljwx-platform PRs after this change:
- PR #830 (12 care-elder files): 15 sections, 3 SSOT chunks, 2 from
  module-map (care-elder/module.yml + ADR-008), 13 from Dify
- 2/4 PRs returned >=1 SSOT chunk; the 0-SSOT PRs were Dependabot /
  CI script changes (correctly no spec to retrieve)
- 0 Cloudflare 1010 errors, 0 query-too-long errors

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 20:02:38 +08:00
knowledge-publisher[bot] 4d1db958fd feat(knowledge): reconcile published records 2026-05-17 11:04:27 +00:00
brunoandClaude Opus 4.7 530e69374a fix(knowledge): set User-Agent in dify_client to bypass Cloudflare 1010
Root-caused the CI knowledge-reconcile failure on
dify.omniverseai.net (run 25980958252). Initially attributed to
GitHub-hosted runner IP being blocked by an IP-based WAF; reality is
Cloudflare's User-Agent filter (error code 1010 = "Browser, device or
User-Agent identity is challenged").

Verified from the same developer IP that produces HTTP 200 with curl:
  python urllib default UA  -> HTTP 403 / "error code: 1010"
  python urllib + Mozilla UA -> HTTP 200
  curl default UA           -> HTTP 200
  curl -A "Python-urllib/.." -> HTTP 403 / "error code: 1010"

dify_client._request was building urllib.Request without a UA header,
so the runtime default "Python-urllib/3.x" tripped the filter on every
call. Fix: set an explicit, identifiable
"ljwx-knowledge-client/1.0 (+repo-url)" UA on every request.

Side benefits beyond unblocking CI:
- developers can now call the public dify.omniverseai.net URL directly
  for local testing instead of port-forwarding the in-cluster service
- requests carry an attributable signature in Dify access logs
- no need for self-hosted runner, WAF allowlist, or reverse-proxy
  workarounds (all dropped from the workflow doc)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 18:58:38 +08:00
brunoandClaude Opus 4.7 3ab99aa940 feat(knowledge): add audience x visibility validator + register W2 buckets
W1 introduced audience / tier / excluded_from_retrieval fields but did
not ship the policy validator documented in
ljwx-workflow/docs/knowledge-as-code-workflow.md §5:
  audience containing claude/codex + visibility=public  -> WARN
  audience containing claude/codex + visibility=customer -> WARN

Implements the validator in common._validate_audience_visibility,
called from load_source_rules after each rule is built. Warnings go
to stderr so existing repos keep validating; CI can grep
"[knowledge:source-config] WARN" to enforce strictly. Customer-visibility
guard is forward-looking (current schema enum is public|internal, but
W-X is planned).

Also registers the two new ljwx-platform source_ids that ljwx-platform
chore/knowledge-w2-bucket-split adds:
  - platform-spec   (tier=proposed)
  - platform-guides (tier=reference)

Re-ingests the full ljwx-platform tree (359 docs normalized, 182
published, 177 invalidated as excluded_from_retrieval=true history).
README is now published (status=published, review_status=approved) —
the previous review_mode=required + stale ledger checksum had left it
invalidated.

Verified end-to-end via query_knowledge against live ljwx-dify-dev:
SSOT chunks return with tier=ssot, guides with tier=reference, no
phase history pollutes results.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 17:20:46 +08:00
bruno 75d317a737 Merge remote-tracking branch 'origin/main' 2026-05-17 13:57:51 +08:00
bruno 3192f5fe55 feat(knowledge): support agent tiers and platform ssot ingest 2026-05-17 13:56:02 +08:00
v3-lite-bot caea6e517b chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@a6e6d10 -> sha256:175e3635adde 2026-05-17 04:37:14 +00:00
brunoandClaude Opus 4.7 42d4f812a1 feat(knowledge): migrate ljwx-platform to new ljwx-dify-dev + fixes
Replays ljwx-platform publish-records into the fresh ljwx-dify-dev
instance after scaling the old dify ns to 0. End-to-end W3 query path
now returns chunk-level citations from live Dify.

Migration:
- 4 ljwx-platform docs re-ingested via reconcile_records.py into the
  newly-created internal-knowledge dataset
  (a786216f-42dd-4a9c-9b7e-0cb04d2eb152)
- 4 republish events recorded
- publish-records now point at the new dataset_id / remote_document_id

Fixes uncovered by the migration:
- reconcile_records: was calling ensure_dataset / upsert_document
  without indexing_technique, so it always tried high_quality which
  needs an embedding model and failed on ljwx-dify-dev's economy
  datasets. Now loads target.indexing_technique from
  publish-targets.yaml (same pattern ingest_artifact already uses).
- dify_client.upsert_document: fallback-to-create only triggered on
  "Document is not available". Stale remote_document_id from old dify
  returns "Document not found" / HTTP 404 instead. Broadened the
  fallback condition so migrations from a wiped Dify don't crash.
- context_pack._build_dify_sections: retrieve always used
  semantic_search, which fails on economy datasets with "Default model
  not found for text-embedding". Now tries semantic first, falls back
  to keyword_search on that specific error.

Smoke verified end-to-end:
- query_knowledge "GitHub workflow CI/CD" against live dify returned 6
  chunk-level hits with citation format
  [kg:repo:doc_id:b3ebae1d-aca1-...] (true Dify retrieval, not local
  keyword fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 11:50:21 +08:00
brunoandClaude Opus 4.7 e80cab384d feat(knowledge): add context_pack, validate_citations, query_knowledge (W3+W4)
Implements the per-task knowledge ingest + query loop described in
ljwx-workflow/docs/knowledge-as-code-workflow.md (W3, W4). Builds the
dev-agent side of the knowledge plane on top of the existing
business-repo -> central ingest -> Dify pipeline.

- dify_client.retrieve(): new POST /datasets/{id}/retrieve binding so
  context_pack can query Dify by text. Returns typed DifyRetrievalHit
  list with chunk_id, document_id, content, score, position.
- context_pack.py: composer. Pipeline = module-map resolution (changed
  files -> modules -> spec/ADR paths) + optional Dify retrieval (when
  KNOWLEDGE_DIFY_BASE_URL + KNOWLEDGE_DIFY_API_KEY env set) + local
  keyword fallback over publish-records content_ref markdown. Outputs
  context-pack.md (for agent consumption) and context-pack.json
  (manifest with chunk_id, source_ref, tier, text_sha256 per section).
  Token-bounded composition with tier-aware ranking; pack header records
  dify_query_at + token_estimate. Gracefully degrades when no Dify
  creds, no module-map, or no records.
- validate_citations.py: extracts and verifies citations in agent
  output. Grammar = [kg:repo:doc_id:chunk_id], [kg:repo:source_path],
  path:Lx-Ly. Produces {verified, tier, severity_cap} per citation;
  unverified caps at info, tier=legacy/proposed/unknown cap at warn,
  tier=ssot/reference allows block. file_line verification is
  bounds-only until W3.2 adds per-chunk line ranges to artifacts.
- query_knowledge.py: thin user-facing query CLI over the same store,
  for local exploration and dogfooding.

Smoke verified end-to-end against ljwx-platform's 4 published records:
query CLI returns ranked matches; context_pack builds a 4-section pack
(4223 tokens) for a 'review' task; validate_citations correctly
classifies 5 synthetic citations (2 verified + 3 unverified, severity
caps applied per rule).

Known limitations (tracked elsewhere):
- All tiers show 'unknown' until W1 schema additions land in publish
  records (audience/tier/excluded_from_retrieval); context_pack handles
  this without crashing.
- Module-map resolution skipped for repos without
  knowledge.module-map.yaml; falls back to keyword + Dify (W3.5).
- file_line citations only verify file existence + line bounds, not
  line content (waiting on W3.2 chunk line ranges).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 10:55:43 +08:00
bruno e6cf453808 fix(dify): migrate legacy plugin packages 2026-05-17 10:20:10 +08:00
bruno 1087ed710e fix(dify): upgrade plugin daemon runtime 2026-05-17 10:09:08 +08:00
bruno 636c125505 fix(dify): route local llm through aliases 2026-05-17 09:57:00 +08:00
bruno cb8d00f355 fix(dify): mount brand logo assets 2026-05-14 00:14:23 +08:00
bruno 2f86185ab6 feat(dify): route default llm to mlx qwen 2026-05-13 23:45:33 +08:00
bruno 554962cb46 fix(cert-manager): use public DNS for DNS01 checks 2026-05-13 22:53:24 +08:00
bruno 543cd00330 fix(ljwx-dify): use omniverseai ingress certificate 2026-05-13 22:49:29 +08:00
bruno 8f37ba3cce fix(ljwx-dify): use local self-signed cert issuer 2026-05-13 22:20:04 +08:00
bruno f174fb973f fix(cert-manager): use nginx http01 solver 2026-05-13 21:59:24 +08:00
bruno fc7be892be chore(gitops): add ljwx-dify dev application 2026-05-13 21:51:38 +08:00
bruno ec344255ee docs(gitops): document ljwx deployment flow 2026-05-13 16:23:32 +08:00
bruno 9663e9a55e fix(platform): route admin websocket to backend 2026-05-13 09:13:21 +08:00
bruno 33fbb86a37 fix(platform): route ingress through nginx 2026-05-13 08:42:28 +08:00
bruno ed165c04c4 fix(platform): use cloudflare issuer for omniverseai 2026-05-13 08:38:03 +08:00
bruno de9f59e90f fix(platform): expose prod on omniverseai domain 2026-05-13 08:30:48 +08:00
deploy-controller-v2[bot] 73a10db14f chore(deploy): apply release-state resolutions [skip ci] 2026-05-12 23:30:13 +00:00
bruno 07d623bbcc fix(gitops): skip prod Harbor login without copy 2026-05-13 07:29:29 +08:00
bruno b8c2bb844c fix(platform): deploy prod images from GHCR 2026-05-13 07:27:32 +08:00
bruno 74adb2951c chore(platform): route prod images through Harbor 2026-05-12 23:17:12 +08:00
v3-lite-bot ec514ce115 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@7abf6eb -> sha256:e70fbe4fc672 2026-05-10 16:16:29 +00:00
v3-lite-bot 62ef4a1625 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@b406619 -> sha256:010105175ae1 2026-05-10 13:28:04 +00:00
v3-lite-bot 01224182d0 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@d1532ff -> sha256:51d477aa6dbb 2026-05-10 12:30:58 +00:00
v3-lite-bot 23ebdcfe91 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@8a95824 -> sha256:e7ea9bbe85f0 2026-05-10 11:26:47 +00:00
v3-lite-bot c1a9be8fcc chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@86fe669 -> sha256:7807e4f99dd4 2026-05-10 11:07:17 +00:00
v3-lite-bot 328ee2f9ca chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@8927425 -> sha256:a35971cc50c3 2026-05-10 10:41:51 +00:00
v3-lite-bot f03143dee9 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@ccd715f -> sha256:528489c03bce 2026-05-10 09:41:04 +00:00
v3-lite-bot ad042aa2c2 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@15a394d -> sha256:e5b8bd4ff406 2026-05-10 08:54:54 +00:00
v3-lite-bot c740f747d4 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@eba478f -> sha256:27d6ffa18803 2026-05-10 07:31:09 +00:00
v3-lite-bot 4c6cb51039 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@fc6051d -> sha256:188bf00dba11 2026-05-10 01:35:12 +00:00
v3-lite-bot acae7bc225 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@09df4f4 -> sha256:74390cb98e16 2026-05-10 00:58:44 +00:00
v3-lite-bot ace1a2bf06 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@2e7bbbb -> sha256:9b3884b06a72 2026-05-09 17:11:10 +00:00
v3-lite-bot 55770afbce chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@766bf21 -> sha256:b20b61e890f8 2026-05-09 00:18:57 +00:00
v3-lite-bot e10ef8df49 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@579f17b -> sha256:d2d5cf04c7dd 2026-05-08 23:15:38 +00:00
v3-lite-bot ef889debf0 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@bba1030 -> sha256:5cc6efeff794 2026-05-08 15:13:44 +00:00
v3-lite-bot 48a12b20a4 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@2ffd36c -> sha256:303cf72213ed 2026-05-08 12:58:51 +00:00
v3-lite-bot 91e4d17e44 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@d367168 -> sha256:5422ec506f9a 2026-05-08 10:46:55 +00:00
v3-lite-bot 5b22ef97de chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@359c553 -> sha256:482b67af5c30 2026-05-08 09:04:05 +00:00
v3-lite-bot fa86e8a5cd chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@3150c41 -> sha256:0d03e9c1ca31 2026-05-08 07:24:26 +00:00
v3-lite-bot 54770b123f chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@e2f2521 -> sha256:bf4adada86ef 2026-05-08 06:47:51 +00:00
v3-lite-bot 505f302aed chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@a99f96e -> sha256:c2e2324a363a 2026-05-08 06:07:19 +00:00
v3-lite-bot bf484b9c52 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@bdf40e6 -> sha256:de7eb3a003f7 2026-05-08 05:36:06 +00:00
v3-lite-bot efda619d98 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@49f59e1 -> sha256:375faab9ea03 2026-05-08 05:12:35 +00:00
v3-lite-bot e07eee8907 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@955f780 -> sha256:a2bb5edb30d6 2026-05-08 04:35:11 +00:00
v3-lite-bot 88e7d92100 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@5362287 -> sha256:b9ae30b52603 2026-05-07 23:59:51 +00:00
v3-lite-bot f223d822dd chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@257f1c4 -> sha256:07986f773f03 2026-05-07 13:42:43 +00:00
v3-lite-bot 9c17d287b7 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@69da981 -> sha256:1c5f00435414 2026-05-07 08:23:37 +00:00
v3-lite-bot 0b4ad6fa6b chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@5943c44 -> sha256:2ed14af991b5 2026-05-07 00:02:12 +00:00
v3-lite-bot f7986f5c10 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@10230b8 -> sha256:2a180f1e199d 2026-05-06 03:07:06 +00:00
v3-lite-bot a1642bd331 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@838b545 -> sha256:012cae4eaf86 2026-05-05 14:11:06 +00:00
v3-lite-bot 2f28c91ec6 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@96bfcf6 -> sha256:e1c19c488d77 2026-05-05 08:54:11 +00:00
v3-lite-bot 548d3cb507 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@4ad6924 -> sha256:2cef34029227 2026-05-05 03:49:23 +00:00
v3-lite-bot 68856b8442 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@f84297a -> sha256:35bd5c0ece69 2026-05-05 03:19:41 +00:00
v3-lite-bot 47049c2593 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@2df1046 -> sha256:ea7f6d496998 2026-05-05 03:15:47 +00:00
v3-lite-bot 6c94bdfa45 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@5bb7cb5 -> sha256:d45bd48f8dfc 2026-05-05 02:51:51 +00:00
v3-lite-bot cecec3f11c chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@e366cc5 -> sha256:c2a7e7c25310 2026-05-05 01:51:04 +00:00
v3-lite-bot a4dd89d991 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@416db6b -> sha256:c32f7b65595b 2026-05-05 01:30:55 +00:00
v3-lite-bot f2238cca7e chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@3862414 -> sha256:7aa38f083154 2026-05-05 01:01:10 +00:00
v3-lite-bot e7acfef3d3 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@f8ae6df -> sha256:ec11478c12c0 2026-05-05 00:00:34 +00:00
v3-lite-bot 318c33e113 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@c18b612 -> sha256:f4ae3cc0d300 2026-05-04 22:58:53 +00:00
v3-lite-bot ec21b5161e chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@efccf78 -> sha256:b4874683dcf1 2026-05-04 22:45:35 +00:00
v3-lite-bot eac3165f5d chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@e29525e -> sha256:09ad9b899a31 2026-05-04 21:39:50 +00:00
v3-lite-bot 8bba2e7f3b chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@03dae42 -> sha256:7174f47f1c44 2026-05-04 15:26:27 +00:00
v3-lite-bot 946faf7feb chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@7f1f28f -> sha256:67057edeec84 2026-05-04 15:09:57 +00:00
v3-lite-bot 06e9d00024 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@c4cc02a -> sha256:0343af96c0fc 2026-05-04 14:43:32 +00:00
v3-lite-bot 4098b5c798 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@8522032 -> sha256:c598048cee0f 2026-05-04 14:31:41 +00:00
v3-lite-bot 038d34b31a chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@9783933 -> sha256:5b6b004a7a44 2026-05-04 14:28:00 +00:00
v3-lite-bot d1a295de45 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@64899e2 -> sha256:dcce819602a0 2026-05-04 12:12:50 +00:00
v3-lite-bot 257cd32f6d chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@5b1ef9a -> sha256:92659175b92c 2026-05-04 12:09:50 +00:00
v3-lite-bot f2ed03bcb8 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@c0cc9e6 -> sha256:8a0204a809af 2026-05-04 09:43:05 +00:00
v3-lite-bot 072d10c50e chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@a28f29e -> sha256:704b59503483 2026-05-04 08:17:36 +00:00
v3-lite-bot 1efd3321fd chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@b614759 -> sha256:d10141ba2246 2026-05-04 07:09:58 +00:00
v3-lite-bot 8d095203e3 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@ce57063 -> sha256:f70c1467ffee 2026-05-04 07:06:59 +00:00
v3-lite-bot f9b722ae8c chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@f7fc601 -> sha256:556224d2ddb2 2026-05-04 05:42:09 +00:00
v3-lite-bot 97209830d3 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@760a479 -> sha256:63c3dccdf898 2026-05-04 05:05:58 +00:00
v3-lite-bot 4f4beae55e chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@ec1c744 -> sha256:61305f6328b3 2026-05-04 04:36:45 +00:00
v3-lite-bot f00e67ddef chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@8298141 -> sha256:5be457103f66 2026-05-04 04:00:20 +00:00
v3-lite-bot 35a08e6a57 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@20ce594 -> sha256:c3e701d1e63a 2026-05-04 03:17:06 +00:00
v3-lite-bot 2f25d82785 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@2e5d91a -> sha256:e9b2a2cf1442 2026-05-04 03:01:41 +00:00
v3-lite-bot b94092034c chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@82c42a8 -> sha256:03ddb8f43a64 2026-05-03 08:54:06 +00:00
v3-lite-bot d36875e4c8 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@5d0619f -> sha256:27a78b548bcc 2026-05-03 07:51:45 +00:00
v3-lite-bot 30bc53d4bb chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@8ee22bd -> sha256:23d5aabac9eb 2026-05-03 05:32:08 +00:00
v3-lite-bot 749cb5f747 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@4a7a818 -> sha256:e58ff8d4fad0 2026-05-03 04:42:52 +00:00
v3-lite-bot 1965543be0 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@027fad0 -> sha256:559de7412180 2026-05-03 04:06:10 +00:00
v3-lite-bot 7941d73335 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@e4b2fac -> sha256:4bb0dde02fc4 2026-05-03 03:51:55 +00:00
v3-lite-bot 837b65d4e8 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@dde88ed -> sha256:38d457acc875 2026-05-03 03:49:00 +00:00
v3-lite-bot e03bca2d49 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@0d89aec -> sha256:7f2a3380228b 2026-05-03 03:30:06 +00:00
v3-lite-bot e2a334b02b chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@582c289 -> sha256:31387387c2a6 2026-05-02 17:19:32 +00:00
bruno fa5faab8eb docs(mqtt-broker): fix bootstrap auth comment typo 2026-05-02 07:52:18 +08:00
brunoandClaude Opus 4.7 6cad875d67 fix(mqtt-broker): bootstrap_users.csv must be world-readable (0444 not 0440)
Verified P0 fix in live cluster after 2438371d sync:
- initContainer rendered csv with "lines: 2" (header + 1 user)
- but EMQX startup logged:
    boostrap_authn_built_in_database_failed:
      reason: Permission denied,
      boostrap_file: /opt/emqx/etc/bootstrap_users.csv,
      boostrap_type: plain
- mosquitto_pub with platform creds got CONNACK 5 (not authorised)

Root cause: initContainer defaults to root, file written as
root:root with mode 0440 (group-readable only). EMQX container
runs as uid=1000 (emqx), neither owner nor in root group, so it
can't read the file. Bootstrap import silently fails and the
broker has zero built-in users → all auth attempts rejected.

Fix: chmod 0444 (world-readable) instead of 0440. The file
sits in an in-memory emptyDir that's pod-scoped and only
contains creds already in emqx-secrets, so widening read
permission inside the pod has no security regression.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 07:51:28 +08:00
bruno f8b439089a fix(mqtt-broker): grant platform bootstrap account privileges 2026-05-02 07:50:47 +08:00
brunoandClaude Opus 4.7 2438371d7f feat(mqtt-broker): wire EMQX bootstrap_users + inject MQTT creds into platform
L0-4 follow-up (post-review): the EMQX broker had emqx-secrets defined but
nothing actually created the platform built-in user, and the platform
deployment did not inject MQTT_USERNAME / MQTT_PASSWORD — so the platform
pod could not authenticate to the broker.

EMQX broker (infra/mqtt-broker):
- statefulset.yaml: add render-bootstrap-users initContainer that renders
  emqx-secrets.EMQX_PLATFORM_USERNAME/PASSWORD into a CSV at
  /etc/emqx-bootstrap/bootstrap_users.csv (memory emptyDir, 64Ki sizeLimit)
- statefulset.yaml: mount that CSV at /opt/emqx/etc/bootstrap_users.csv in
  the EMQX container (subPath, read-only)
- (already in 50857d9): emqx.conf points authentication[0] at
  bootstrap_file with bootstrap_type=plain — EMQX hashes on import
- README updated with kubectl create secret command

Platform deployment (apps/ljwx-platform):
- base/deployment.yaml: add `secretRef: ljwx-platform-mqtt` to envFrom
  (optional: true, so noop / non-MQTT environments still start)
- overlays/prod/ljwx-platform-mqtt.secret.example.yaml: example schema
- overlays/prod/README.md: documents the kubectl create secret command,
  mirroring the existing ljwx-platform-db pattern

This pairs with the platform-side refactor in
ljwx-platform@a6befe4 (split publish/filter validation, async first-connect
retry, QoS-preserving reconnect). Both must be deployed together.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 21:01:51 +08:00
brunoandClaude Opus 4.7 50857d9a60 fix(mqtt-broker): enable EMQX dashboard listener on 18083
Initial L0-4 emqx.conf only configured listeners.tcp + listeners.ws,
so dashboard (port 18083) never started even though Service exposed it.

Add dashboard.listeners.http {bind = 0.0.0.0:18083} block + bind admin
password to existing EMQX_DASHBOARD__DEFAULT_PASSWORD env (already in
secret.yaml).

Verified via gitops:
- ArgoCD App-of-Apps auto-registered infra-mqtt-broker (no manual step)
- ArgoCD synced from origin/main
- emqx-0 pod Running 1/1
- 1883 / 8083 reachable; 18083 was refused (this fix)
- Post-fix, port-forward svc/mqtt-broker 18083:18083 should hit Dashboard

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:45:05 +08:00
brunoandClaude Opus 4.7 e4ca10b6c6 feat(infra): L0-4 EMQX MQTT broker as platform infra
新增 mqtt-broker namespace,与 infra/postgres + infra/redis + infra/arc
同级,作为 ljwx-platform 单体的基础组件。

ljwx-platform 仓 L0-4 代码侧的部署对接(详见
ljwx-platform/docs/care/decisions/0010-mqtt-broker-strategy.md)。

infra/mqtt-broker/:
- namespace.yaml          (mqtt-broker namespace)
- configmap.yaml          (EMQX 5.x HOCON 配置:1883 mqtt + 8083 ws +
                          18083 dashboard + sha256 password auth +
                          built_in_database ACL deny-by-default)
- secret.yaml             (cookie + dashboard password + platform 账号,
                          生产环境必须替换 REPLACE_ME 占位)
- pvc.yaml                (5G PVC,保 retained / offline messages)
- statefulset.yaml        (emqx/emqx:5.7.2 单副本,256m-1Gi memory,
                          tcp readinessProbe + livenessProbe)
- service.yaml            (Headless + ClusterIP)
- kustomization.yaml + README.md

argocd-apps/20-infra-mqtt-broker.yaml:
  ArgoCD Application,自动同步,与 infra-arc 同款 syncPolicy。

apps/ljwx-platform/overlays/{k3s-eu,prod}/runtime-config.yaml:
  注入 MQTT_KIND=emqx + MQTT_BROKER_URL +
  mqtt-broker.mqtt-broker.svc.cluster.local:1883。

Phase 1 不做(按项目启用):mTLS / EMQX 集群 / 国密 SM4 / 客户专属 PKI。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 20:20:12 +08:00
v3-lite-bot d0f6d394ee chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@4e26377 -> sha256:a2fb92e2ec79 2026-05-01 01:27:47 +00:00
v3-lite-bot 52ae7e6f49 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@50ba30c -> sha256:f7fa4400104a 2026-05-01 00:53:16 +00:00
v3-lite-bot 0fe06230c3 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@ce351c6 -> sha256:66304483fe40 2026-05-01 00:48:02 +00:00
v3-lite-bot dfa9b337b8 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@003a763 -> sha256:c6c30238a966 2026-05-01 00:06:07 +00:00
v3-lite-bot 77b0a09ef8 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@ae1e6f4 -> sha256:deab39f153d5 2026-04-30 23:06:18 +00:00
v3-lite-bot cb2930cc60 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@552cf2b -> sha256:72d9570676a9 2026-04-30 22:52:12 +00:00
v3-lite-bot 45bbfe31e6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ac49c6d -> sha256:3a970b671ff1 2026-04-29 14:05:23 +00:00
v3-lite-bot a85ba83ea9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3223cdb -> sha256:d41417042faf 2026-04-29 13:56:27 +00:00
v3-lite-bot f7a5cec94f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@2bfc4cf -> sha256:b1b33a537532 2026-04-29 13:49:07 +00:00
v3-lite-bot 9d0c4c4c24 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f571b30 -> sha256:afc3d39a7ecd 2026-04-29 13:37:57 +00:00
v3-lite-bot 8cbded85ab chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@38bdf2a -> sha256:3cc2d5f30093 2026-04-24 07:51:02 +00:00
v3-lite-bot ab7090bcdc chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@c0f779b -> sha256:7b1e2bc00410 2026-04-22 10:51:15 +00:00
v3-lite-bot 0489ed9d94 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@6a533b5 -> sha256:3d6d3c07dfcb 2026-04-22 03:41:43 +00:00
v3-lite-bot 887d9223ba chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@d13687c -> sha256:e96853f9aea3 2026-04-20 10:44:46 +00:00
v3-lite-bot 86251e1dd3 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@d62f20b -> sha256:2795742cda0e 2026-04-19 15:09:26 +00:00
v3-lite-bot 96d7f04b56 chore(zhihu-engine-new): deploy lingjingwanxiang/zhihu-engine-new@b93cf43 -> sha256:b42d7180d9dd 2026-04-18 13:30:15 +00:00
v3-lite-bot d1ba9f78bf chore(zhihu-engine-new): deploy lingjingwanxiang/zhihu-engine-new@74af420 -> sha256:f60e4780316e 2026-04-18 10:20:47 +00:00
v3-lite-bot d16451795c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b333c0a -> sha256:1a60ebf66d03 2026-04-18 04:43:26 +00:00
v3-lite-bot fa9c3c1164 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@735a188 -> sha256:909231e2451b 2026-04-18 04:36:56 +00:00
v3-lite-bot bab5baeada chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@34152f3 -> sha256:968212d26610 2026-04-18 04:30:05 +00:00
v3-lite-bot 13d987316d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d4109b9 -> sha256:cfc0b8bef0d3 2026-04-18 04:23:32 +00:00
v3-lite-bot e3d8c1aa3c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d2ca8ed -> sha256:907bb10addb5 2026-04-18 04:16:54 +00:00
v3-lite-bot 80cf11ed17 chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@8dab743 -> sha256:cde1cb0865dd 2026-04-17 12:55:50 +00:00
brunoandClaude Sonnet 4.6 79643e0d9e feat(ljwx-platform): add k3s-eu overlay and ArgoCD Application
- apps/ljwx-platform/overlays/k3s-eu/ — new dev overlay with initial
  image digests; ci-deploy.yml (v3-lite) will patch this on every push
- argocd-apps/60-ljwx-platform-dev.yaml — ArgoCD Application pointing
  to the k3s-eu overlay (auto-discovered via *-dev.yaml pattern)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 20:52:11 +08:00
BrunoGao 0b1838a17d Merge pull request #275 from lingjingwanxiang/bot/queue-ljwx-platform-24564532535
queue(ljwx-platform): 20260417T122602Z-ljwx-platform-sha-dc7ec41
2026-04-17 20:26:05 +08:00
github-actions[bot] f1aaf469b8 queue(ljwx-platform): 20260417T122602Z-ljwx-platform-sha-dc7ec41 2026-04-17 12:26:02 +00:00
v3-lite-bot c1471efb8e chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@dc7ec41 -> sha256:6796e9bb6d7d 2026-04-17 12:17:49 +00:00
v3-lite-bot 813f700ae6 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@886ac35 -> sha256:6c7c6202c420 2026-04-16 13:30:51 +00:00
v3-lite-bot 9fd2338a2d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5979f0f -> sha256:ef135f9a1501 2026-04-13 07:50:04 +00:00
v3-lite-bot d79cc349dc chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@208ca1f -> sha256:ff46a29cabe1 2026-04-13 06:32:13 +00:00
brunoandClaude Opus 4.6 ce7e3a0c7d fix(ljwx-health): increase startup probe timeout for Spring Boot
Spring Boot startup takes >330s on k3s (Flyway + MyBatis + bean init).
Increase startup probe: initialDelay 30→60s, failureThreshold 30→60,
timeout 5→10s. Total allowed startup time: 60 + 60*10 = 660s.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 11:09:38 +08:00
v3-lite-bot cbeac70d19 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b14495d -> sha256:25640de6f048 2026-04-13 02:53:49 +00:00
v3-lite-bot eb8808df47 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@e024f97 -> sha256:51cfa12857be 2026-04-13 02:30:35 +00:00
v3-lite-bot 15f1a4930f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c7ed9bf -> sha256:112652077681 2026-04-12 11:18:30 +00:00
v3-lite-bot 1cfa95f7e9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4beab46 -> sha256:016fd305a891 2026-04-12 11:07:31 +00:00
v3-lite-bot ec8f460bae chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9d970b8 -> sha256:1aa001bd2462 2026-04-12 09:35:56 +00:00
bruno e3b789b281 fix(zhihu-engine-new): write celerybeat-schedule to /tmp for non-root 2026-04-12 11:15:41 +08:00
bruno 45ec70d979 fix(zhihu-engine-new): remove PreSync hook entirely — not needed 2026-04-12 11:02:26 +08:00
bruno d1672c0b2a fix(zhihu-engine-new): simplify alembic hook to busybox no-op 2026-04-12 10:49:26 +08:00
bruno 6ce9d4a0e8 fix(zhihu-engine-new): add workingDir /app to alembic job 2026-04-12 10:37:26 +08:00
v3-lite-bot eb596e24c6 chore(zhihu-engine-new): deploy lingjingwanxiang/zhihu-engine-new@509f574 -> sha256:2b0e9360e876 2026-04-12 02:33:32 +00:00
brunoandClaude Opus 4.6 5c1dfb39e1 feat(zhihu-engine-new): onboard to GitOps
- Kustomize base: backend, celery worker/beat, service, ingress (nginx)
- Dev overlay with GHCR image
- ArgoCD Application with auto-sync + selfHeal
- Alembic PreSync hook for DB migrations
- Infra PG (postgres-lb.infra.svc) + Redis (redis-lb.infra.svc)
- Added to release/services.yaml for promoter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:29:35 +08:00
brunoandClaude Opus 4.6 f2ffc2a5d6 fix(ljwx-health): simplify PreSync hooks to busybox no-ops
DB and Flyway are handled by infra PG + Spring Boot on startup.
PreSync jobs no longer need postgres image or DB credentials.
Replaced with busybox echo to pass ArgoCD sync-wave ordering.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:29:04 +08:00
brunoandClaude Opus 4.6 bd5c64e36a fix(ljwx-health): switch ingress from traefik to nginx
Traefik has been removed from the cluster. Only nginx ingressClass exists.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 09:24:30 +08:00
brunoandClaude Opus 4.6 d4d98c577b fix(ljwx-health): remove DB dependency from bigscreen deployment
BigScreen (Flask) does not use database directly. Removed:
- wait-for-db initContainer
- MYSQL_* environment variables

BigScreen only needs Redis and the ljwx-boot API backend.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 08:44:53 +08:00
brunoandClaude Opus 4.6 06c75cc374 fix(ljwx-health): migrate PreSync jobs from MySQL to PostgreSQL
init-db and flyway-repair jobs were using mysql:8.0 image and connecting
to mysql-lb.infra.svc. Switched to postgres:14-alpine with PG-compatible
commands. Both jobs now do connectivity checks rather than destructive ops.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 08:39:16 +08:00
v3-lite-bot bd79fec11b chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1e107c8 -> sha256:282814fe8ed0 2026-04-11 23:37:10 +00:00
v3-lite-bot e9089f24de chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@00c5179 -> sha256:51bba7c41840 2026-04-11 00:44:01 +00:00
v3-lite-bot ca5b20ea6f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@cf2a043 -> sha256:421205cd65b5 2026-04-10 08:19:15 +00:00
v3-lite-bot 98896e060a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5394f27 -> sha256:7355807986bc 2026-04-10 07:09:02 +00:00
v3-lite-bot 33c96049fa chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7cda281 -> sha256:4d54242ac600 2026-04-10 03:11:37 +00:00
v3-lite-bot 471f7d5279 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7526fbe -> sha256:03ee74a1e46d 2026-04-10 01:39:19 +00:00
v3-lite-bot e5826501cd chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@1f65ded -> sha256:637e5e00f670 2026-04-10 01:12:01 +00:00
v3-lite-bot 25aad8c4d4 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@2cbf907 -> sha256:17de89ff648c 2026-04-10 00:03:47 +00:00
v3-lite-bot ce0e960b30 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@77a677c -> sha256:b1f326cdc3e0 2026-04-09 23:53:23 +00:00
v3-lite-bot b636c70896 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@beed177 -> sha256:d00096e3ad90 2026-04-09 23:42:45 +00:00
v3-lite-bot 0e25d54f8a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5335405 -> sha256:175b236a6b8d 2026-04-09 23:35:02 +00:00
v3-lite-bot 263fde696c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a79fb41 -> sha256:82580ec908c3 2026-04-09 16:07:11 +00:00
v3-lite-bot 6682b7888d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@afe980d -> sha256:a306bd6e87b8 2026-04-09 15:54:50 +00:00
v3-lite-bot fbb7c5dfb3 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@56ef393 -> sha256:4998804b248f 2026-04-09 15:34:16 +00:00
v3-lite-bot 6fc67fbffe chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@24f7bdc -> sha256:0bc85c79da4e 2026-04-09 15:05:24 +00:00
v3-lite-bot 5323abaf8e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@78b7f8a -> sha256:b8cdfd31fe08 2026-04-09 14:52:58 +00:00
v3-lite-bot 115c5df5d7 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@70aea83 -> sha256:a4ad981143e4 2026-04-09 14:43:42 +00:00
v3-lite-bot a8ae64fde7 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c270b33 -> sha256:8f309b0e3bb2 2026-04-09 14:34:19 +00:00
v3-lite-bot eb12380f5e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d386cf4 -> sha256:2351f4882c04 2026-04-09 14:11:00 +00:00
v3-lite-bot b9f7d687e1 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b6943a0 -> sha256:5989442dffae 2026-04-09 13:34:35 +00:00
v3-lite-bot fad824d902 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@5bc99eb -> sha256:b7cccbccd3e3 2026-04-09 13:24:23 +00:00
v3-lite-bot c7a93de129 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0fc3abc -> sha256:02fe3fcdcfc2 2026-04-09 13:15:28 +00:00
v3-lite-bot 25e2f9c949 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b2d9d1a -> sha256:9c853f1c50aa 2026-04-09 13:08:11 +00:00
v3-lite-bot a5d3289d42 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@d49ab33 -> sha256:382648bf1085 2026-04-09 13:00:48 +00:00
v3-lite-bot ec8420d49e chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@abe7f17 -> sha256:740ec7c7749d 2026-04-09 12:51:56 +00:00
v3-lite-bot d2e8d59dd9 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@53c5043 -> sha256:edc2bac1fe38 2026-04-09 11:59:34 +00:00
v3-lite-bot 4ded8f6723 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9b15b21 -> sha256:24560c942c86 2026-04-09 11:26:56 +00:00
v3-lite-bot dfd93ae8b0 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9715d0c -> sha256:56558877be7f 2026-04-09 11:17:13 +00:00
v3-lite-bot 193c40d1cc chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@3835841 -> sha256:91dfb02ea3ba 2026-04-09 09:00:01 +00:00
v3-lite-bot 466fe2ad36 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@f932e30 -> sha256:03ab6d597513 2026-04-09 08:26:11 +00:00
v3-lite-bot 55fc6d81c2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@79e9da1 -> sha256:e49ae15d5e20 2026-04-09 08:19:12 +00:00
v3-lite-bot 20d84010e8 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@9990830 -> sha256:d535c9053d72 2026-04-09 07:41:28 +00:00
deploy-promoter[bot] 83ebf85934 chore(deploy): remove ljwx workloads from k3s 2026-04-09 09:35:16 +02:00
v3-lite-bot b6bdc0bdee chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@ea5b41a -> sha256:a918b396c426 2026-04-09 05:43:15 +00:00
v3-lite-bot 6df7a8217f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@98c36ea -> sha256:c508c11848a5 2026-04-09 04:50:34 +00:00
github-actions[bot] 5532b28266 chore(n8n): deploy ljwx-n8n@2f35e5c [sha256:6105aa86e8e4] 2026-04-09 02:29:03 +00:00
BrunoGao 632490bc09 fix(chat,openclaw): migrate ingress to nginx + omniverseai.net domain 2026-04-09 10:12:24 +08:00
BrunoGao 7adcf10040 fix(omni-website): point ArgoCD source to GitHub instead of Gitea 2026-04-09 10:08:28 +08:00
BrunoGao 1780f0a5b4 fix(omni-website): migrate ingress from traefik to nginx class 2026-04-09 10:04:51 +08:00
BrunoGao f759285784 fix(n8n): use nginx ingress class for omniverseai.net domain 2026-04-09 09:32:33 +08:00
BrunoGao 6a8a829840 fix(n8n): change domain to n8n.omniverseai.net 2026-04-09 09:25:59 +08:00
BrunoGao 747bffcbdc fix(n8n): use short sha tag (sha-60f94c9) to avoid pull issues 2026-04-09 09:20:31 +08:00
BrunoGao 74136a4363 fix(n8n): use image.tag instead of digest (chart doesn't support digest) 2026-04-09 09:13:33 +08:00
BrunoGao 4465d1cb8e chore(n8n): deploy ljwx-n8n@60f94c9 -> sha256:e32ed434a91e9 2026-04-09 09:10:55 +08:00
v3-lite-bot a2ba18944d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b3738ab -> sha256:ec9f16e9374c 2026-04-08 17:46:52 +00:00
v3-lite-bot 47d4b74b3c chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@b3738ab -> sha256:bf403ea5298d 2026-04-08 16:48:22 +00:00
v3-lite-bot d32292cc9b chore(omni-stock): deploy lingjingwanxiang/omni-stock@1f1d245 -> sha256:8cbd0a4c5ad9 2026-04-08 16:21:03 +00:00
v3-lite-bot 72c6414dfa chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@1e12e61 -> sha256:f1acba4b7716 2026-04-08 16:08:20 +00:00
v3-lite-bot f5efb1d111 chore(omni-stock): deploy lingjingwanxiang/omni-stock@48a37cc -> sha256:f532c05270d2 2026-04-08 16:04:56 +00:00
v3-lite-bot 3f79959371 chore(omni-stock): deploy lingjingwanxiang/omni-stock@48a37cc -> sha256:ea3ec5b3dc74 2026-04-08 15:48:41 +00:00
v3-lite-bot 98d4caeaa9 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@97b566d -> sha256:032c31c4bbb3 2026-04-08 15:45:14 +00:00
v3-lite-bot b99fb83d46 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@e09bf79 -> sha256:4fceab6d86b8 2026-04-08 15:25:47 +00:00
v3-lite-bot ae43f40e7b chore(omni-stock): deploy lingjingwanxiang/omni-stock@f30f64b -> sha256:5453d89986c7 2026-04-08 15:19:42 +00:00
knowledge-publisher[bot] e23a9ea5f3 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@ccfba2c466 2026-04-08 14:54:11 +00:00
deploy-promoter[bot] 6d197cc608 promote(ops): queue normalize [skip ci] 2026-04-08 08:04:09 +00:00
bruno d400d44dba fix: use Gitea repoURL for omni-website ArgoCD app 2026-04-08 15:41:57 +08:00
v3-lite-bot 154365dcb0 chore(omni-website): deploy lingjingwanxiang/omni-website@04b6fa4 -> sha256:358d85128ba5 2026-04-08 06:44:39 +00:00
BrunoGao 888153836d feat: register omni-website-dev namespace and application in cluster kustomization 2026-04-08 14:34:19 +08:00
BrunoGao 797ddc3566 feat: add omni-website-dev ArgoCD application 2026-04-08 14:33:56 +08:00
BrunoGao 8f319aacd8 feat: add omni-website-dev namespace with quotas and limits 2026-04-08 14:33:47 +08:00
BrunoGao c035fd62d0 feat: add omni-website dev overlay ingress with TLS 2026-04-08 14:33:38 +08:00
BrunoGao 2015e48026 feat: add omni-website dev overlay kustomization 2026-04-08 14:33:25 +08:00
BrunoGao be976b90e6 feat: add omni-website base kustomization 2026-04-08 14:33:18 +08:00
BrunoGao 311774230f feat: add omni-website base ingress manifest 2026-04-08 14:33:11 +08:00
BrunoGao ec50198cde feat: add omni-website base service manifest 2026-04-08 14:33:03 +08:00
BrunoGao dd5fc2ec48 feat: add omni-website base deployment manifest 2026-04-08 14:32:56 +08:00
v3-lite-bot 694f98974d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@2a0156c -> sha256:ab0c04ca49e4 2026-04-08 06:09:20 +00:00
v3-lite-bot a014e6affe chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@0452432 -> sha256:7f54a250b1ab 2026-04-07 13:40:07 +00:00
v3-lite-bot 9a9d54f23a chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c93bacc -> sha256:6758e8f0db06 2026-04-07 12:41:56 +00:00
knowledge-publisher[bot] 51735328d4 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-health@c93baccdd2 2026-04-07 11:45:39 +00:00
v3-lite-bot 697bdb4f11 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@f0e4c0d -> sha256:c0f5a275bbcb 2026-04-07 11:12:01 +00:00
v3-lite-bot 3b52e741d6 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@336af21 -> sha256:e88aaa8e8ec9 2026-04-07 10:49:16 +00:00
v3-lite-bot a46c9a8e8e chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@98a06e2 -> sha256:589dfc14fad0 2026-04-07 10:26:36 +00:00
v3-lite-bot 3a78882d29 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@bd9a22d -> sha256:a28e30f6e505 2026-04-07 09:54:21 +00:00
v3-lite-bot c6414e460d chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@9c15bc2 -> sha256:38002f0b9369 2026-04-07 09:35:15 +00:00
v3-lite-bot d66ab3c4aa chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@d7727c7 -> sha256:7b586b364214 2026-04-07 09:14:33 +00:00
v3-lite-bot bb0e71556c chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@ebfeebe -> sha256:397adc672011 2026-04-07 08:44:00 +00:00
v3-lite-bot c82e825096 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@20eee77 -> sha256:46e95aa3735b 2026-04-07 08:23:24 +00:00
BrunoGao 4ff90701e2 chore(deploy): update 4 services to latest GHCR digests + add omni-stock/zhihu-engine to service maps
Phase 1: Update overlay digests for ljwx-health (3 images) and ljwx-shiti (1 image)
Phase 2: Add omni-stock and zhihu-engine to both service mapping files
2026-04-07 15:41:14 +08:00
v3-lite-bot 4da726df97 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@4ef7f0e -> sha256:48ad8bfa6f1d 2026-04-07 07:30:19 +00:00
v3-lite-bot e4500fe46f chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@7744814 -> sha256:bec1ef33f554 2026-04-07 06:33:20 +00:00
v3-lite-bot a1d11f35d0 chore(ljwx-openclaw): deploy lingjingwanxiang/ljwx-openclaw@a118d1b -> sha256:b309acda3e0f 2026-04-07 06:08:02 +00:00
v3-lite-bot 5c4126db3b chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@bbce38c -> sha256:3c4c0adb40eb 2026-04-07 06:04:32 +00:00
v3-lite-bot f3a6ea161f chore(ljwx-openclaw): deploy lingjingwanxiang/ljwx-openclaw@7af5a81 -> sha256:c55e8fcec7c6 2026-04-07 05:48:00 +00:00
v3-lite-bot dc2650c551 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@6cdbfea -> sha256:656f443fb3ff 2026-04-07 05:45:06 +00:00
BrunoGao 677127d7f7 ci: remove monthly-rollback-drill scheduled workflow
No longer using nightly/scheduled workflows per org policy.
2026-04-07 13:22:21 +08:00
BrunoGao beae975ba8 ci: remove nightly-evidence scheduled workflow
No longer using nightly/scheduled workflows per org policy.
2026-04-07 13:22:20 +08:00
BrunoGao 7c18a770b0 ci: remove nightly-auto-repair scheduled workflow
No longer using nightly/scheduled workflows per org policy.
2026-04-07 13:22:18 +08:00
knowledge-publisher[bot] d1df001f37 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@018c943767 2026-04-07 04:49:10 +00:00
knowledge-publisher[bot] c3dc108b71 feat(knowledge): ingest artifact from lingjingwanxiang/ljwx-platform@5c9ac24584 2026-04-07 04:47:07 +00:00
v3-lite-bot 722d24d60e chore(agent-platform): deploy lingjingwanxiang/agent-platform@2bded61 -> sha256:bb9b65cc570b 2026-04-07 00:49:20 +00:00
v3-lite-bot 60166a6873 chore(agent-platform): deploy lingjingwanxiang/agent-platform@b960cb7 -> sha256:e47f179b14a6 2026-04-07 00:45:15 +00:00
v3-lite-bot ccdab6f4cb chore(agent-platform): deploy lingjingwanxiang/agent-platform@70ced03 -> sha256:29ce88e8ea25 2026-04-07 00:41:08 +00:00
v3-lite-bot 44466710f6 chore(agent-platform): deploy lingjingwanxiang/agent-platform@55510d3 -> sha256:fddb1fc72c08 2026-04-07 00:35:31 +00:00
BrunoGao 38935eaaf1 fix: update source-registry repo names from BrunoGao/BrunoGaoSZ to lingjingwanxiang org 2026-04-07 08:31:02 +08:00
v3-lite-bot d200bedc50 chore(ljwx-openclaw): deploy lingjingwanxiang/ljwx-openclaw@3d6a638 -> sha256:6352ffa3e9b0 2026-04-06 17:42:48 +00:00
v3-lite-bot e792307e6a chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@8bd2102 -> sha256:370fe24e2bc2 2026-04-06 17:41:51 +00:00
v3-lite-bot 52d7df356c chore(ljwx-openclaw): deploy lingjingwanxiang/ljwx-openclaw@f9a7fce -> sha256:6c5695b82b26 2026-04-06 17:22:31 +00:00
v3-lite-bot 760b68cbc0 chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@385fb8f -> sha256:97f15f61b808 2026-04-06 17:22:04 +00:00
v3-lite-bot 2ffdc0a5fd chore(ljwx-bookstore): deploy lingjingwanxiang/ljwx-bookstore@37d1e97 -> sha256:025eca402778 2026-04-06 17:17:19 +00:00
v3-lite-bot 7cf9388c9f chore(agent-platform): deploy lingjingwanxiang/agent-platform@0e7a02a -> sha256:b14d6150067b 2026-04-06 17:10:48 +00:00
v3-lite-bot d3e20aab01 chore(ljwx-shiti): deploy lingjingwanxiang/ljwx-shiti@a9e054b -> sha256:b8206fcfe78f 2026-04-06 17:08:06 +00:00
v3-lite-bot 4833601a4d chore(agent-platform): deploy lingjingwanxiang/agent-platform@f23f1d2 -> sha256:9f1e61f05f35 2026-04-06 17:06:39 +00:00
BrunoGao 3fd29d91d3 chore: switch to GitHub hosted runners 2026-04-07 01:04:59 +08:00
BrunoGao f95ac7dd15 chore: switch to GitHub hosted runners 2026-04-07 01:04:57 +08:00
BrunoGao aa54360b3e chore: switch to GitHub hosted runners 2026-04-07 01:04:55 +08:00
BrunoGao bff67fe448 chore: switch to GitHub hosted runners 2026-04-07 01:04:52 +08:00
BrunoGao 12156c8d05 chore: switch to GitHub hosted runners 2026-04-07 01:04:49 +08:00
BrunoGao 60740a2d5d chore: switch to GitHub hosted runners 2026-04-07 01:04:47 +08:00
BrunoGao fb37808821 chore: switch to GitHub hosted runners 2026-04-07 01:04:44 +08:00
BrunoGao 1bc4a74d49 chore: switch to GitHub hosted runners 2026-04-07 01:04:41 +08:00
BrunoGao 94e5f8b950 chore: switch to GitHub hosted runners 2026-04-07 01:04:38 +08:00
BrunoGao c565fa784d chore: switch to GitHub hosted runners 2026-04-07 01:04:33 +08:00
v3-lite-bot ade72ff83f chore(agent-platform-demo): deploy lingjingwanxiang/agent-platform-demo@d04f217 -> sha256:4f007ee49dbc 2026-04-06 17:03:56 +00:00
v3-lite-bot 9fc88205e5 chore(ljwx-core-api): deploy lingjingwanxiang/ljwx-core-api@aa4ea0f -> sha256:299a6f337443 2026-04-06 17:03:46 +00:00
v3-lite-bot 5771d92b46 chore(zhihu-engine): deploy lingjingwanxiang/zhihu-engine@b0bc8ee -> sha256:6d8dc4dfd6bb 2026-04-05 16:37:38 +00:00
f2ce00b927 chore(zhihu-engine): scaffold base + dev overlay + ArgoCD Application (#272)
Generated by generate-overlay.sh from zhihu-engine/deploy.yaml.
Components: backend (port 8000) + frontend (port 5173).
ArgoCD auto-sync enabled for dev.

Co-authored-by: deploy-promoter[bot] <deploy-promoter[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-06 00:26:22 +08:00
deploy-promoter[bot]andClaude Opus 4.6 9bdd67f4d2 fix(ljwx-tuoyu): switch base image to GHCR, drop harbor ref
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:22:21 +02:00
BrunoGao 93a48766a0 Merge pull request #274 from lingjingwanxiang/task/fix-omni-stock-env-20260405-181917
fix(omni-stock): inject env secret into backend
2026-04-06 00:20:38 +08:00
deploy-promoter[bot]andClaude Opus 4.6 8b5e24838e fix(omni-stock): inject env secret into backend deployment
Add envFrom secretRef for omni-stock-env (DATABASE_URL, TUSHARE_TOKEN, TZ).
Increase initialDelaySeconds for startup with DB migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:20:08 +02:00
deploy-promoter[bot]andClaude Opus 4.6 1d34a4f283 fix(ljwx-tuoyu): match kustomize image name to harbor base ref
Base deployment uses harbor image name; overlay must match it
to rewrite to GHCR digest.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:20:03 +02:00
deploy-promoter[bot] c5f974b95b promote(dev): ljwx-tuoyu sha-2888635 [skip ci] 2026-04-05 18:19:27 +02:00
BrunoGao 918e78734b Merge pull request #273 from lingjingwanxiang/bot/queue-ljwx-tuoyu-24005423950
queue(ljwx-tuoyu): 20260405T161720Z-ljwx-tuoyu-sha-2888635
2026-04-06 00:17:23 +08:00
github-actions[bot] 2bc2181c4b queue(ljwx-tuoyu): 20260405T161720Z-ljwx-tuoyu-sha-2888635 2026-04-05 16:17:21 +00:00
deploy-promoter[bot]andClaude Opus 4.6 fc18837a3e fix(ljwx-tuoyu): correct containerPort 80→8000 and health paths
Uvicorn listens on 8000. Probes should hit /healthz and /readyz.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 18:16:48 +02:00
deploy-promoter[bot]andClaude Opus 4.6 2687660581 fix(overlays): point kustomize image entries to GHCR
Consolidate duplicate brunogao→harbor entries to point directly to
lingjingwanxiang GHCR digests. Fixes kustomize image name matching
so ArgoCD renders correct GHCR images.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:12:05 +02:00
deploy-promoter[bot] 7e3a1d65ad promote(dev): 5 services [skip ci] 2026-04-05 17:06:57 +02:00
BrunoGao 1b47e74a59 Merge pull request #271 from lingjingwanxiang/task/add-queue-entries-20260405-170337
queue: add qlib-predict and sync-service
2026-04-05 23:05:37 +08:00
deploy-promoter[bot]andClaude Opus 4.6 2551a32979 queue: add qlib-predict and sync-service entries
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 17:05:28 +02:00
BrunoGao 1a5003b4ae Merge pull request #270 from lingjingwanxiang/bot/queue-ljwx-tuoyu-24004030046
queue(ljwx-tuoyu): 20260405T145837Z-ljwx-tuoyu-sha-62e27ec
2026-04-05 22:58:39 +08:00
github-actions[bot] 53959571e5 queue(ljwx-tuoyu): 20260405T145837Z-ljwx-tuoyu-sha-62e27ec 2026-04-05 14:58:37 +00:00
BrunoGao 1e37dabb7a Merge pull request #268 from lingjingwanxiang/bot/queue-ljwx-stock-qlib-bootstrap-24003982772
queue(ljwx-stock-qlib-bootstrap): 20260405T145613Z-ljwx-stock-qlib-bootstrap-sha-876076f
2026-04-05 22:56:15 +08:00
github-actions[bot] 6f10894396 queue(ljwx-stock-qlib-bootstrap): 20260405T145613Z-ljwx-stock-qlib-bootstrap-sha-876076f 2026-04-05 14:56:14 +00:00
BrunoGao b3ed3ce5c9 Merge pull request #267 from lingjingwanxiang/bot/queue-ljwx-stock-kline-etl-24003982774
queue(ljwx-stock-kline-etl): 20260405T145550Z-ljwx-stock-kline-etl-sha-876076f
2026-04-05 22:55:52 +08:00
github-actions[bot] f93e753537 queue(ljwx-stock-kline-etl): 20260405T145550Z-ljwx-stock-kline-etl-sha-876076f 2026-04-05 14:55:50 +00:00
BrunoGao fa60c44d7d Merge pull request #266 from lingjingwanxiang/bot/queue-ljwx-stock-agent-24003982773
queue(ljwx-stock-agent): 20260405T145524Z-ljwx-stock-agent-sha-876076f
2026-04-05 22:55:27 +08:00
github-actions[bot] dd185318a0 queue(ljwx-stock-agent): 20260405T145524Z-ljwx-stock-agent-sha-876076f 2026-04-05 14:55:25 +00:00
BrunoGao 8489ba3cfe Merge pull request #265 from lingjingwanxiang/task/fix-ghcr-namespace-20260405-165425
fix(services): correct GHCR namespace brunogao → lingjingwanxiang
2026-04-05 22:55:11 +08:00
deploy-promoter[bot]andClaude Opus 4.6 655c404b43 fix(services): correct GHCR namespace from brunogao to lingjingwanxiang
All GHCR packages are under the org namespace. brunogao personal
namespace causes push permission denied errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:54:55 +02:00
BrunoGao 219594eee7 Merge pull request #264 from lingjingwanxiang/task/fix-omni-stock-pull-secret-20260405-164957
fix(omni-stock): add imagePullSecrets for GHCR pull
2026-04-05 22:50:40 +08:00
deploy-promoter[bot]andClaude Opus 4.6 de86eaff0d fix(omni-stock): add imagePullSecrets for GHCR pull
Pods fail with ErrImagePull without ghcr-pull secret reference.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:50:21 +02:00
BrunoGao d16ec47dbc Merge pull request #263 from lingjingwanxiang/task/ghcr-services-map-20260405-163819
feat(services): migrate stock/tuoyu/sync-service to GHCR
2026-04-05 22:39:14 +08:00
deploy-promoter[bot]andClaude Opus 4.6 e8e700daa1 feat(services): migrate stock/tuoyu/sync-service to GHCR in service map
Update kustomizeImageName to ghcr.io for ljwx-stock (4 services),
ljwx-tuoyu, and sync-service across all environment variants.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 16:38:44 +02:00
v3-lite-bot 348f9518d5 chore(omni-stock): deploy lingjingwanxiang/omni-stock@718cceb -> sha256:e1c1e851e435 2026-04-05 14:19:08 +00:00
BrunoGao 4eaea7ac40 Merge pull request #262 from lingjingwanxiang/task/onboard-omni-stock-20260405-154913
chore(omni-stock): scaffold base + dev overlay + ArgoCD app
2026-04-05 21:50:07 +08:00
deploy-promoter[bot]andClaude Opus 4.6 c2ea288d8f chore(omni-stock): scaffold base + dev overlay + ArgoCD app
V3-lite onboarding for omni-stock ServiceGroup (backend + frontend).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 15:49:38 +02:00
v3-lite-bot 050c985ca3 chore(agent-platform): deploy lingjingwanxiang/agent-platform@22a3d16 -> sha256:718ea4b53b24 2026-04-05 10:44:25 +00:00
v3-lite-bot 11cea625cf chore(agent-platform): deploy lingjingwanxiang/agent-platform@680e404 -> sha256:0bb3c20d28aa 2026-04-05 10:32:53 +00:00
v3-lite-bot dc18ad0530 chore(ljwx-dify): deploy lingjingwanxiang/ljwx-dify@7705b50 -> sha256:0c4c4cf61fb2 2026-04-05 08:06:03 +00:00
v3-lite-bot 7ace376c65 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@c01947b -> sha256:1309dc0ab66e 2026-04-05 01:49:09 +00:00
v3-lite-bot dc34eafd2c chore(ljwx-chat): deploy lingjingwanxiang/ljwx-chat@1cb7a11 -> sha256:1be7de88b745 2026-04-05 00:52:26 +00:00
v3-lite-bot 7e6cd4cc26 chore(ljwx-dify): deploy lingjingwanxiang/ljwx-dify@fb0fe1b -> sha256:720c8131ad8e 2026-04-05 00:43:16 +00:00
v3-lite-bot 06ec423b3b chore(ljwx-platform): deploy lingjingwanxiang/ljwx-platform@2d0f6a9 -> sha256:61318f5505c3 2026-04-04 18:45:31 +00:00
v3-lite-bot 9d7d91cc2d chore(ljwx-bookstore): deploy lingjingwanxiang/ljwx-bookstore@97bcc95 -> sha256:a5e917ac538d 2026-04-04 17:12:14 +00:00
v3-lite-bot b3a50efe74 chore(ljwx-openclaw): deploy lingjingwanxiang/ljwx-openclaw@7df4af2 -> sha256:d1de094f88ab 2026-04-04 16:56:00 +00:00
v3-lite-bot e4f1545840 chore(agent-platform): deploy lingjingwanxiang/agent-platform@158c54a -> sha256:7a47bc419e54 2026-04-04 16:39:49 +00:00
BrunoGao 012d4c26ca Merge pull request #261 from lingjingwanxiang/fix/ljwx-website-port
fix(ljwx-website): port 80 → 3000
2026-04-05 00:21:51 +08:00
deploy-promoter[bot]andClaude Opus 4.6 08b854fffb fix(ljwx-website): containerPort 80 → 3000 (Next.js listens on 3000)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 18:21:35 +02:00
v3-lite-bot 0712427491 chore(ljwx-website): deploy lingjingwanxiang/ljwx-website@8958cd4 -> sha256:23d29630c2ee 2026-04-04 15:15:26 +00:00
v3-lite-bot 66d7e27988 chore(ljwx-shiti): deploy lingjingwanxiang/ljwx-shiti@475d700 -> sha256:a355cbe9ff85 2026-04-04 15:09:35 +00:00
v3-lite-bot ce01d56bb9 chore(agent-platform-demo): deploy lingjingwanxiang/agent-platform-demo@ca04b59 -> sha256:b5e0144d218e 2026-04-04 15:07:58 +00:00
v3-lite-bot 67f286772d chore(ljwx-core-api): deploy lingjingwanxiang/ljwx-core-api@8db4bd1 -> sha256:a814e0529adc 2026-04-04 15:07:19 +00:00
v3-lite-bot 6cd1cd6775 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a254d34 -> sha256:f8d010d49d46 2026-04-04 13:32:31 +00:00
BrunoGao 2d7e47a878 Merge pull request #260 from lingjingwanxiang/feat/infra-timescaledb
fix(infra): switch to timescaledb:latest-pg16 (uid compat)
2026-04-04 21:16:32 +08:00
deploy-promoter[bot]andClaude Opus 4.6 1457e6bd23 fix(infra): use timescaledb:latest-pg16 instead of HA (uid compat)
timescaledb-ha uses uid 1000, existing PVC owned by uid 999.
timescaledb:latest-pg16 uses uid 70 (standard postgres) which is
compatible with existing data directory permissions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:16:11 +02:00
BrunoGao 02fa2afa21 Merge pull request #259 from lingjingwanxiang/feat/infra-timescaledb
feat(infra): PostgreSQL → TimescaleDB HA
2026-04-04 21:13:21 +08:00
deploy-promoter[bot]andClaude Opus 4.6 bdccbb2b34 feat(infra): switch PostgreSQL to TimescaleDB HA image
- Image: pgvector/pgvector:pg16 → timescale/timescaledb-ha:pg16
- Add shared_preload_libraries=timescaledb
- TimescaleDB HA includes pgvector, no functionality loss

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 15:13:05 +02:00
v3-lite-bot 36472f2f77 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a56fd50 -> sha256:128732c9610b 2026-04-04 12:31:19 +00:00
BrunoGao 3008896710 Merge pull request #258 from lingjingwanxiang/feat/ljwx-health-switch-postgresql
feat(ljwx-health): switch MySQL → PostgreSQL
2026-04-04 20:19:01 +08:00
deploy-promoter[bot]andClaude Opus 4.6 b11a2f737b feat(ljwx-health): switch from MySQL to PostgreSQL
- ConfigMap: datasource URL/driver/host → PostgreSQL
- Flyway: re-enable with migration-pg scripts
- Init containers: wait for PostgreSQL instead of MySQL
- DB_PASSWORD secret patched to PostgreSQL credentials

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 14:18:42 +02:00
BrunoGao b57810858b Merge pull request #257 from lingjingwanxiang/fix/ljwx-health-disable-flyway
fix(ljwx-health): disable Flyway for MySQL 8.0
2026-04-04 19:10:32 +08:00
deploy-promoter[bot]andClaude Opus 4.6 0b65195817 fix(ljwx-health): disable Flyway — incompatible with MySQL 8.0
Flyway 10.10.0 dropped MySQL 8.0 support. Disable until PostgreSQL migration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:10:07 +02:00
v3-lite-bot ee5553370d chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@6a2f6c8 -> sha256:7d6d89c1e740 2026-04-04 11:07:15 +00:00
v3-lite-bot 31322114a2 chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@a2f4062 -> sha256:c2a61258ea5d 2026-04-04 10:50:32 +00:00
BrunoGao 4e5ab00fe7 Merge pull request #256 from lingjingwanxiang/feat/ljwx-health-ghcr-pull
feat(ljwx-health): add ghcr-pull for V3-lite
2026-04-04 17:53:07 +08:00
deploy-promoter[bot]andClaude Opus 4.6 a582ba3f9b feat(ljwx-health): add ghcr-pull imagePullSecret for V3-lite GHCR migration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 11:50:58 +02:00
v3-lite-bot 3983ba89ff chore(ljwx-health): deploy lingjingwanxiang/ljwx-health@8f42f14 -> sha256:92d9b584d414 2026-04-04 07:47:37 +00:00
deploy-controller-v2[bot] 5375ca4855 chore(deploy): apply release-state resolutions [skip ci] 2026-04-03 07:57:14 +00:00
deploy-controller-v2[bot] 47a2ca07ed chore(deploy): apply release-state resolutions [skip ci] 2026-04-03 07:55:25 +00:00
deploy-controller-v2[bot] 68bda70ac6 chore(deploy): apply release-state resolutions [skip ci] 2026-04-03 07:54:51 +00:00
BrunoGao 2bfeab9cb7 fix(openclaw): use openai-completions api type (#255) 2026-04-02 00:50:36 +08:00
BrunoGaoanddeploy-promoter[bot] f142eb7528 fix(openclaw): switch primary model to ruishi/gpt-5 (#253)
chatgpt.com/backend-api returns 403 (blocked by CloudFlare),
and apiproxy.aicodeditor.com returns 502 (proxy down).

Switch to ruishi/gpt-5 via ruishiglobal.com as primary model,
with anthropic/claude-sonnet-4-6 as fallback for when the
Anthropic proxy recovers.

Also populate anthropic model list for gateway routing.

Co-authored-by: deploy-promoter[bot] <deploy-promoter[bot]@users.noreply.github.com>
2026-04-02 00:42:06 +08:00
Codex ba72dd7cab feat(gitops): resolve dev deploy images from ghcr profiles 2026-03-29 08:44:09 +02:00
deploy-promoter[bot] d5d749207d feat(gitops): onboard ljwx-tuoyu dev baseline 2026-03-25 14:57:36 +01:00
deploy-promoter[bot] 3461778a78 fix(gitops): unblock bigscreen and stock-agent rollout 2026-03-25 10:18:57 +01:00
deploy-promoter[bot] 5701ffafb3 fix(gitops): repair k3s runtime contracts 2026-03-25 10:13:12 +01:00
deploy-controller-v2[bot] 1610c3a215 chore(deploy): apply release-state resolutions [skip ci] 2026-03-20 13:04:50 +00:00
deploy-controller-v2[bot] 63946224c1 chore(deploy): apply release-state resolutions [skip ci] 2026-03-20 12:57:27 +00:00
deploy-controller-v2[bot] a4cd9562dc chore(deploy): apply release-state resolutions [skip ci] 2026-03-17 11:21:57 +00:00
deploy-controller-v2[bot] 84468ebefb chore(deploy): apply release-state resolutions [skip ci] 2026-03-17 07:46:32 +00:00
Codex 802fac17db fix(deploy): avoid secret checks in if clauses 2026-03-17 08:45:47 +01:00
Codex 20fcafe089 fix(deploy): reuse deploy token for state callbacks 2026-03-17 08:44:35 +01:00
Codex 2ae216f41a fix(deploy): wrap result dispatch payload 2026-03-17 07:46:34 +01:00
Codex eb10a8dd79 fix(deploy): harden gitops v2 controller 2026-03-17 07:20:10 +01:00
Codex f16d8ef709 fix(deploy): prefer mapped registry target in v2 apply 2026-03-17 05:12:38 +01:00
BrunoGaoSZ 24de83d6dd feat(deploy): cut over to state-driven controller 2026-03-17 02:27:33 +01:00
smoke-runner[bot] 3bdd71e0ce chore(smoke): update smoke evidence [skip ci] 2026-03-17 01:21:33 +00:00
smoke-runner[bot] a59607380d chore(smoke): update smoke evidence [skip ci] 2026-03-17 01:06:48 +00:00
smoke-runner[bot] 49de4b6491 chore(smoke): update smoke evidence [skip ci] 2026-03-17 00:51:30 +00:00
smoke-runner[bot] 5541ce7711 chore(smoke): update smoke evidence [skip ci] 2026-03-17 00:36:34 +00:00
smoke-runner[bot] 7bce28ab5c chore(smoke): update smoke evidence [skip ci] 2026-03-17 00:21:31 +00:00
smoke-runner[bot] 6ebdad4069 chore(smoke): update smoke evidence [skip ci] 2026-03-17 00:06:46 +00:00
smoke-runner[bot] 0f75eb6f98 chore(smoke): update smoke evidence [skip ci] 2026-03-16 23:51:29 +00:00
smoke-runner[bot] 6f04191d38 chore(smoke): update smoke evidence [skip ci] 2026-03-16 23:36:35 +00:00
smoke-runner[bot] 21bc0cd93d chore(smoke): update smoke evidence [skip ci] 2026-03-16 23:21:34 +00:00
smoke-runner[bot] 0ad5d650a9 chore(smoke): update smoke evidence [skip ci] 2026-03-16 23:06:56 +00:00
smoke-runner[bot] 6b29dbf04e chore(smoke): update smoke evidence [skip ci] 2026-03-16 22:51:29 +00:00
smoke-runner[bot] c9f06da301 chore(smoke): update smoke evidence [skip ci] 2026-03-16 22:36:33 +00:00
smoke-runner[bot] 4403b43b12 chore(smoke): update smoke evidence [skip ci] 2026-03-16 22:22:51 +00:00
smoke-runner[bot] 0c70ee2c08 chore(smoke): update smoke evidence [skip ci] 2026-03-16 22:06:36 +00:00
smoke-runner[bot] f5b9d0977b chore(smoke): update smoke evidence [skip ci] 2026-03-16 21:51:29 +00:00
smoke-runner[bot] e6d6b2704d chore(smoke): update smoke evidence [skip ci] 2026-03-16 21:36:35 +00:00
smoke-runner[bot] 75f0a9cb82 chore(smoke): update smoke evidence [skip ci] 2026-03-16 21:21:37 +00:00
smoke-runner[bot] 07a8763210 chore(smoke): update smoke evidence [skip ci] 2026-03-16 21:06:37 +00:00
smoke-runner[bot] 1804342652 chore(smoke): update smoke evidence [skip ci] 2026-03-16 20:51:32 +00:00
smoke-runner[bot] c25c9b608e chore(smoke): update smoke evidence [skip ci] 2026-03-16 20:36:35 +00:00
smoke-runner[bot] 1abbe6fe04 chore(smoke): update smoke evidence [skip ci] 2026-03-16 20:21:34 +00:00
smoke-runner[bot] f8ccd6e51a chore(smoke): update smoke evidence [skip ci] 2026-03-16 20:06:38 +00:00
smoke-runner[bot] bb105927a4 chore(smoke): update smoke evidence [skip ci] 2026-03-16 19:51:29 +00:00
smoke-runner[bot] 70a71a2479 chore(smoke): update smoke evidence [skip ci] 2026-03-16 19:36:33 +00:00
smoke-runner[bot] 66b75e92b0 chore(smoke): update smoke evidence [skip ci] 2026-03-16 19:21:34 +00:00
smoke-runner[bot] 3da30ebd89 chore(smoke): update smoke evidence [skip ci] 2026-03-16 19:06:57 +00:00
smoke-runner[bot] caef7eae4d chore(smoke): update smoke evidence [skip ci] 2026-03-16 18:51:30 +00:00
smoke-runner[bot] 74bc6c1ca7 chore(smoke): update smoke evidence [skip ci] 2026-03-16 18:36:35 +00:00
smoke-runner[bot] 6b0ea31c37 chore(smoke): update smoke evidence [skip ci] 2026-03-16 18:21:32 +00:00
smoke-runner[bot] ece73f7da7 chore(smoke): update smoke evidence [skip ci] 2026-03-16 18:06:38 +00:00
smoke-runner[bot] 07ce83ebb5 chore(smoke): update smoke evidence [skip ci] 2026-03-16 17:51:32 +00:00
smoke-runner[bot] 7a3e58c043 chore(smoke): update smoke evidence [skip ci] 2026-03-16 17:36:36 +00:00
smoke-runner[bot] 4c9a5be242 chore(smoke): update smoke evidence [skip ci] 2026-03-16 17:21:35 +00:00
smoke-runner[bot] b6ff594ae3 chore(smoke): update smoke evidence [skip ci] 2026-03-16 17:06:46 +00:00
smoke-runner[bot] 0be67c4883 chore(smoke): update smoke evidence [skip ci] 2026-03-16 16:51:32 +00:00
smoke-runner[bot] 7edd52346b chore(smoke): update smoke evidence [skip ci] 2026-03-16 16:36:41 +00:00
smoke-runner[bot] 390fb92508 chore(smoke): update smoke evidence [skip ci] 2026-03-16 16:21:36 +00:00
smoke-runner[bot] f28dfd157c chore(smoke): update smoke evidence [skip ci] 2026-03-16 16:06:38 +00:00
smoke-runner[bot] edd372b8d8 chore(smoke): update smoke evidence [skip ci] 2026-03-16 15:51:35 +00:00
smoke-runner[bot] 6acfb1eeac chore(smoke): update smoke evidence [skip ci] 2026-03-16 15:36:41 +00:00
smoke-runner[bot] 291a82352f chore(smoke): update smoke evidence [skip ci] 2026-03-16 15:21:36 +00:00
deploy-promoter[bot] a1d3571a06 promote(dev): agent-platform sha-5819f97 [skip ci] 2026-03-16 15:10:26 +00:00
BrunoGao 1117279550 Merge pull request #246 from lingjingwanxiang/bot/queue-agent-platform-23150270051
queue(agent-platform): 20260316T150657Z-agent-platform-sha-5819f97
2026-03-16 23:07:01 +08:00
github-actions[bot] 31dbb55944 queue(agent-platform): 20260316T150657Z-agent-platform-sha-5819f97 2026-03-16 15:06:57 +00:00
smoke-runner[bot] a76521d282 chore(smoke): update smoke evidence [skip ci] 2026-03-16 15:06:46 +00:00
smoke-runner[bot] 0fdf5c0039 chore(smoke): update smoke evidence [skip ci] 2026-03-16 14:51:39 +00:00
smoke-runner[bot] b0bb8cbb37 chore(smoke): update smoke evidence [skip ci] 2026-03-16 14:36:38 +00:00
smoke-runner[bot] 21fe6d4cbe chore(smoke): update smoke evidence [skip ci] 2026-03-16 14:21:38 +00:00
smoke-runner[bot] 2e8ad60a04 chore(smoke): update smoke evidence [skip ci] 2026-03-16 14:07:43 +00:00
smoke-runner[bot] c46954d6e5 chore(smoke): update smoke evidence [skip ci] 2026-03-16 13:51:37 +00:00
smoke-runner[bot] 4dfeb27976 chore(smoke): update smoke evidence [skip ci] 2026-03-16 13:36:38 +00:00
smoke-runner[bot] ac8756cd23 chore(smoke): update smoke evidence [skip ci] 2026-03-16 13:21:42 +00:00
smoke-runner[bot] d171612ed8 chore(smoke): update smoke evidence [skip ci] 2026-03-16 13:06:37 +00:00
smoke-runner[bot] f6e5844b49 chore(smoke): update smoke evidence [skip ci] 2026-03-16 12:51:36 +00:00
smoke-runner[bot] 409ec92d42 chore(smoke): update smoke evidence [skip ci] 2026-03-16 12:36:37 +00:00
smoke-runner[bot] 83b5555ca1 chore(smoke): update smoke evidence [skip ci] 2026-03-16 12:21:37 +00:00
smoke-runner[bot] 7cc6be0a20 chore(smoke): update smoke evidence [skip ci] 2026-03-16 12:06:52 +00:00
BrunoGaoSZ ec8839e0d2 fix(arc): add harbor pull secret to runner service accounts 2026-03-16 12:51:47 +01:00
deploy-promoter[bot] 7168fb1b6d promote(dev): agent-platform sha-926e56f [skip ci] 2026-03-16 11:46:55 +00:00
BrunoGao 35f101c129 Merge pull request #244 from lingjingwanxiang/bot/queue-agent-platform-23140181665
queue(agent-platform): 20260316T114319Z-agent-platform-sha-926e56f
2026-03-16 19:43:23 +08:00
7129 changed files with 761398 additions and 8792 deletions
+24
View File
@@ -0,0 +1,24 @@
Refs https://github.com/OWNER/REPOSITORY/issues/NUMBER
<!-- LJWX Health operations paths require a full cross-repository issue URL. -->
## Scope
-
## Validation Evidence
- Run / artifact URI:
- Expected versus observed:
## Risk and Rollback
- Risk:
- Rollback:
## Checklist
- [ ] Linked issue has an owner, scope, rollback, and acceptance/evidence contract
- [ ] GitOps render and relevant policy gates pass
- [ ] No plaintext Secret is committed
- [ ] Emergency rollback exception, if used, has the controlled label and approval
+5 -5
View File
@@ -15,17 +15,17 @@ permissions:
jobs:
build:
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
runs-on: arc-build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Setup Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Login GHCR
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -51,7 +51,7 @@ jobs:
} >> "${GITHUB_OUTPUT}"
- name: Build and push promoter image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v7
with:
context: .
file: scripts/promoter/Dockerfile
+193
View File
@@ -0,0 +1,193 @@
name: deploy-controller-v2
on:
repository_dispatch:
types:
- deploy-resolution-v2
workflow_dispatch:
inputs:
release_state_repo:
description: "Release state repository"
required: false
default: "lingjingwanxiang/ljwx-release-state"
type: string
release_state_ref:
description: "Release state branch"
required: false
default: "main"
type: string
env_allowlist:
description: "Comma-separated environment allowlist"
required: false
default: "dev,demo,prod"
type: string
concurrency:
group: deploy-controller-v2-${{ github.event_name == 'repository_dispatch' && (github.event.client_payload.release_state_ref || 'main') || inputs.release_state_ref }}
cancel-in-progress: false
jobs:
apply:
runs-on: ubuntu-latest
permissions:
contents: write
packages: read
steps:
- uses: actions/checkout@v6
- name: Checkout release-state repository
uses: actions/checkout@v6
with:
repository: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.release_state_repo || inputs.release_state_repo }}
ref: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.release_state_ref || inputs.release_state_ref }}
path: release-state
token: ${{ secrets.RELEASE_STATE_REPO_TOKEN != '' && secrets.RELEASE_STATE_REPO_TOKEN || secrets.DEPLOY_REPO_TOKEN != '' && secrets.DEPLOY_REPO_TOKEN || github.token }}
- name: Set up uv
uses: astral-sh/setup-uv@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Login GHCR
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Login EU Harbor
shell: bash
env:
HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
run: |
set -euo pipefail
if [[ -z "${HARBOR_USERNAME}" || -z "${HARBOR_PASSWORD}" ]]; then
echo "Skip EU Harbor login"
exit 0
fi
printf '%s' "${HARBOR_PASSWORD}" | docker login harbor.eu.lingjingwanxiang.cn --username "${HARBOR_USERNAME}" --password-stdin
- name: Apply resolutions
shell: bash
env:
ENV_ALLOWLIST: ${{ github.event_name == 'repository_dispatch' && (github.event.client_payload.env_allowlist || 'dev,demo,prod') || inputs.env_allowlist }}
run: |
set -euo pipefail
uvx --with pyyaml python scripts/gitops_v2/apply_resolutions.py \
--repo-root . \
--state-root ./release-state \
--env-allowlist "${ENV_ALLOWLIST}" \
--result-out /tmp/deploy-results.json
- name: Copy prod images
shell: bash
env:
HARBOR_PROD_USERNAME: ${{ secrets.HARBOR_PROD_USERNAME }}
HARBOR_PROD_PASSWORD: ${{ secrets.HARBOR_PROD_PASSWORD }}
run: |
set -euo pipefail
NEEDS_COPY="$(python3 - <<'PY'
import json
from pathlib import Path
payload = json.loads(Path("/tmp/deploy-results.json").read_text(encoding="utf-8"))
needs_copy = False
for item in payload.get("results", []):
if item.get("env") != "prod":
continue
for fact in item.get("facts", []):
if not isinstance(fact, dict) or fact.get("type") != "component":
continue
value = fact.get("value", {})
if isinstance(value, dict) and value.get("copyRequired") is True:
needs_copy = True
print("true" if needs_copy else "false")
PY
)"
if [[ "${NEEDS_COPY}" != "true" ]]; then
echo "No prod registry copy required"
exit 0
fi
if [[ -z "${HARBOR_PROD_USERNAME}" || -z "${HARBOR_PROD_PASSWORD}" ]]; then
echo "缺少 HARBOR_PROD_USERNAME/HARBOR_PROD_PASSWORD,无法执行 prod controller 镜像复制" >&2
exit 1
fi
printf '%s' "${HARBOR_PROD_PASSWORD}" | docker login harbor.omniverseai.net --username "${HARBOR_PROD_USERNAME}" --password-stdin
python3 scripts/gitops_v2/copy_resolution_images.py --result-file /tmp/deploy-results.json
- name: Commit deploy changes
shell: bash
run: |
set -euo pipefail
if git diff --quiet; then
echo "No deploy overlay changes"
exit 0
fi
git config user.name "deploy-controller-v2[bot]"
git config user.email "deploy-controller-v2[bot]@users.noreply.github.com"
git add apps argocd-apps cluster cluster-prod release
git commit -m "chore(deploy): apply release-state resolutions [skip ci]"
git pull --rebase origin "${GITHUB_REF_NAME}"
git push origin "HEAD:${GITHUB_REF_NAME}"
- name: Dispatch release results
shell: bash
env:
RELEASE_STATE_REPO: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.release_state_repo || inputs.release_state_repo }}
RELEASE_STATE_TOKEN: ${{ secrets.RELEASE_STATE_REPO_TOKEN != '' && secrets.RELEASE_STATE_REPO_TOKEN || secrets.DEPLOY_REPO_TOKEN }}
run: |
set -euo pipefail
RESULT_COUNT="$(python3 - <<'PY'
import json
from pathlib import Path
payload = json.loads(Path("/tmp/deploy-results.json").read_text(encoding="utf-8"))
print(len(payload.get("results", [])))
PY
)"
if [[ "${RESULT_COUNT}" == "0" ]]; then
echo "No deploy results to dispatch"
exit 0
fi
if [[ -z "${RELEASE_STATE_TOKEN}" ]]; then
echo "缺少 RELEASE_STATE_REPO_TOKEN 或 DEPLOY_REPO_TOKEN,deploy result 无法回写到 release-state" >&2
exit 1
fi
python3 - <<'PY'
import json
import os
import urllib.error
import urllib.request
from pathlib import Path
owner, repo = os.environ["RELEASE_STATE_REPO"].split("/", 1)
payload = json.loads(Path("/tmp/deploy-results.json").read_text(encoding="utf-8"))
for item in payload.get("results", []):
body = {
"event_type": "deploy-result-v2",
"client_payload": {
"result": item,
},
}
request = urllib.request.Request(
f"https://api.github.com/repos/{owner}/{repo}/dispatches",
data=json.dumps(body).encode("utf-8"),
method="POST",
headers={
"Accept": "application/vnd.github+json",
"Authorization": f"Bearer {os.environ['RELEASE_STATE_TOKEN']}",
"Content-Type": "application/json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "ljwx-deploy",
},
)
try:
with urllib.request.urlopen(request) as response:
if response.status not in (200, 201, 204):
raise SystemExit(f"result dispatch failed http={response.status}")
except urllib.error.HTTPError as exc:
body_text = exc.read().decode("utf-8", errors="ignore")
raise SystemExit(f"result dispatch failed http={exc.code}: {body_text}")
PY
+49 -4
View File
@@ -10,11 +10,11 @@ permissions:
jobs:
gate:
runs-on: arc-ci
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
@@ -64,13 +64,27 @@ jobs:
--changed-files .changed-files.txt \
--event-path "${{ github.event_path }}"
- name: Enforce LJWX Health operations issue contract
env:
# DEPLOY_REPO_TOKEN is the existing cross-repository automation token.
# Fork PRs do not receive it and therefore remain fail-closed.
GITHUB_TOKEN: ${{ secrets.DEPLOY_REPO_TOKEN || github.token }}
run: |
set -euo pipefail
python3 -m unittest discover -s scripts/tests -p 'test_ops_issue_link_gate.py' -v
python3 scripts/ci/ops_issue_link_gate.py \
--profile deploy \
--repository "${GITHUB_REPOSITORY}" \
--event-path "${GITHUB_EVENT_PATH}" \
--changed-files .changed-files.txt
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@v6
- name: Smart CS contract gate
run: bash scripts/ci/check-smart-cs-contract.sh
@@ -81,6 +95,37 @@ jobs:
- name: Plaintext secret scan
run: bash scripts/ci/check-no-plain-secrets.sh .changed-files.txt
- name: Validate registry pull secret source mapping
run: python3 -m unittest scripts.tests.test_registry_pull_secret_sync -v
- name: Validate Cloudflare DNS token contract
run: python3 -m unittest scripts.tests.test_cloudflare_dns_token_contract -v
- name: Validate LJWX Health alert acknowledgement relay
run: |
node --check apps/openclaw/base/alert-relay/server.mjs
node --test apps/openclaw/base/alert-relay/server.test.mjs
python3 -m py_compile \
scripts/operations/ack-alert-delivery.py \
scripts/operations/run-alert-delivery-drill.py
kubectl kustomize apps/openclaw/overlays/openclaw-dev >/tmp/openclaw-dev.yaml
- name: Validate LJWX Health failure-domain scheduling
run: |
uvx --with pyyaml python -m unittest \
scripts.tests.test_ljwx_health_failure_domain_scheduling -v
uvx --with pyyaml python -m unittest \
scripts.tests.test_ljwx_health_contabo_migration -v
- name: Validate LJWX Health schema rollout
run: |
uvx --with pyyaml python -m unittest scripts.tests.test_ljwx_health_release_promotion -v
uvx --with pyyaml python -m unittest scripts.tests.test_ljwx_health_schema_restore_drill -v
shellcheck apps/ljwx-health/schema-restore-drill/scripts/*.sh
kubectl kustomize apps/ljwx-health/overlays/k3s-eu >/tmp/ljwx-health-k3s-eu.yaml
kubectl kustomize apps/ljwx-health/overlays/contabo-prod >/tmp/ljwx-health-contabo-prod.yaml
kubectl kustomize apps/ljwx-health/overlays/contabo-prod-staging >/tmp/ljwx-health-contabo-prod-staging.yaml
- name: Detect pgvector related changes
id: pgvector
run: |
@@ -48,23 +48,23 @@ concurrency:
jobs:
promote:
runs-on: arc-ci
runs-on: ubuntu-latest
env:
KNOWLEDGE_DIFY_BASE_URL: ${{ secrets.KNOWLEDGE_DIFY_BASE_URL }}
KNOWLEDGE_DIFY_API_KEY: ${{ secrets.KNOWLEDGE_DIFY_API_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@v6
- name: Resolve inputs
id: prep
@@ -72,17 +72,21 @@ jobs:
run: |
set -euo pipefail
if [ "${GITHUB_EVENT_NAME}" = "push" ]; then
echo "target_env=dev" >> "${GITHUB_OUTPUT}"
echo "source_ref=${GITHUB_REF}" >> "${GITHUB_OUTPUT}"
echo "source_sha=${GITHUB_SHA}" >> "${GITHUB_OUTPUT}"
echo "diff_from=${{ github.event.before }}" >> "${GITHUB_OUTPUT}"
echo "skip_dify=false" >> "${GITHUB_OUTPUT}"
{
echo "target_env=dev"
echo "source_ref=${GITHUB_REF}"
echo "source_sha=${GITHUB_SHA}"
echo "diff_from=${{ github.event.before }}"
echo "skip_dify=false"
} >> "${GITHUB_OUTPUT}"
else
echo "target_env=${{ inputs.target_env }}" >> "${GITHUB_OUTPUT}"
echo "source_ref=${{ inputs.source_ref }}" >> "${GITHUB_OUTPUT}"
echo "source_sha=${{ inputs.source_sha || github.sha }}" >> "${GITHUB_OUTPUT}"
echo "diff_from=${{ inputs.diff_from }}" >> "${GITHUB_OUTPUT}"
echo "skip_dify=${{ inputs.skip_dify }}" >> "${GITHUB_OUTPUT}"
{
echo "target_env=${{ inputs.target_env }}"
echo "source_ref=${{ inputs.source_ref }}"
echo "source_sha=${{ inputs.source_sha || github.sha }}"
echo "diff_from=${{ inputs.diff_from }}"
echo "skip_dify=${{ inputs.skip_dify }}"
} >> "${GITHUB_OUTPUT}"
fi
- name: Validate staged candidate artifacts
@@ -36,22 +36,22 @@ concurrency:
jobs:
review:
runs-on: arc-ci
runs-on: ubuntu-latest
env:
KNOWLEDGE_GITHUB_TOKEN: ${{ secrets.DEPLOY_REPO_TOKEN || github.token }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@v6
- name: Resolve inputs
id: prep
@@ -59,17 +59,21 @@ jobs:
run: |
set -euo pipefail
if [ "${GITHUB_EVENT_NAME}" = "repository_dispatch" ]; then
echo "source_repo=${{ github.event.client_payload.source_repo }}" >> "${GITHUB_OUTPUT}"
echo "source_run_id=${{ github.event.client_payload.source_run_id }}" >> "${GITHUB_OUTPUT}"
echo "artifact_name=${{ github.event.client_payload.artifact_name }}" >> "${GITHUB_OUTPUT}"
echo "target_env=${{ github.event.client_payload.target_env || 'dev' }}" >> "${GITHUB_OUTPUT}"
echo "source_sha=${{ github.event.client_payload.source_sha || '' }}" >> "${GITHUB_OUTPUT}"
{
echo "source_repo=${{ github.event.client_payload.source_repo }}"
echo "source_run_id=${{ github.event.client_payload.source_run_id }}"
echo "artifact_name=${{ github.event.client_payload.artifact_name }}"
echo "target_env=${{ github.event.client_payload.target_env || 'dev' }}"
echo "source_sha=${{ github.event.client_payload.source_sha || '' }}"
} >> "${GITHUB_OUTPUT}"
else
echo "source_repo=${{ inputs.source_repo }}" >> "${GITHUB_OUTPUT}"
echo "source_run_id=${{ inputs.source_run_id }}" >> "${GITHUB_OUTPUT}"
echo "artifact_name=${{ inputs.artifact_name }}" >> "${GITHUB_OUTPUT}"
echo "target_env=${{ inputs.target_env }}" >> "${GITHUB_OUTPUT}"
echo "source_sha=" >> "${GITHUB_OUTPUT}"
{
echo "source_repo=${{ inputs.source_repo }}"
echo "source_run_id=${{ inputs.source_run_id }}"
echo "artifact_name=${{ inputs.artifact_name }}"
echo "target_env=${{ inputs.target_env }}"
echo "source_sha="
} >> "${GITHUB_OUTPUT}"
fi
- name: Stage candidate artifact
+20 -16
View File
@@ -43,24 +43,24 @@ concurrency:
jobs:
ingest:
runs-on: arc-ci
runs-on: ubuntu-latest
env:
KNOWLEDGE_DIFY_BASE_URL: ${{ secrets.KNOWLEDGE_DIFY_BASE_URL }}
KNOWLEDGE_DIFY_API_KEY: ${{ secrets.KNOWLEDGE_DIFY_API_KEY }}
KNOWLEDGE_GITHUB_TOKEN: ${{ secrets.DEPLOY_REPO_TOKEN || github.token }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@v6
- name: Resolve inputs
id: prep
@@ -68,19 +68,23 @@ jobs:
run: |
set -euo pipefail
if [ "${GITHUB_EVENT_NAME}" = "repository_dispatch" ]; then
echo "source_repo=${{ github.event.client_payload.source_repo }}" >> "${GITHUB_OUTPUT}"
echo "source_run_id=${{ github.event.client_payload.source_run_id }}" >> "${GITHUB_OUTPUT}"
echo "artifact_name=${{ github.event.client_payload.artifact_name }}" >> "${GITHUB_OUTPUT}"
echo "target_env=${{ github.event.client_payload.target_env || 'dev' }}" >> "${GITHUB_OUTPUT}"
echo "skip_dify=${{ github.event.client_payload.skip_dify || 'false' }}" >> "${GITHUB_OUTPUT}"
echo "source_sha=${{ github.event.client_payload.source_sha || '' }}" >> "${GITHUB_OUTPUT}"
{
echo "source_repo=${{ github.event.client_payload.source_repo }}"
echo "source_run_id=${{ github.event.client_payload.source_run_id }}"
echo "artifact_name=${{ github.event.client_payload.artifact_name }}"
echo "target_env=${{ github.event.client_payload.target_env || 'dev' }}"
echo "skip_dify=${{ github.event.client_payload.skip_dify || 'false' }}"
echo "source_sha=${{ github.event.client_payload.source_sha || '' }}"
} >> "${GITHUB_OUTPUT}"
else
echo "source_repo=${{ inputs.source_repo }}" >> "${GITHUB_OUTPUT}"
echo "source_run_id=${{ inputs.source_run_id }}" >> "${GITHUB_OUTPUT}"
echo "artifact_name=${{ inputs.artifact_name }}" >> "${GITHUB_OUTPUT}"
echo "target_env=${{ inputs.target_env }}" >> "${GITHUB_OUTPUT}"
echo "skip_dify=${{ inputs.skip_dify }}" >> "${GITHUB_OUTPUT}"
echo "source_sha=" >> "${GITHUB_OUTPUT}"
{
echo "source_repo=${{ inputs.source_repo }}"
echo "source_run_id=${{ inputs.source_run_id }}"
echo "artifact_name=${{ inputs.artifact_name }}"
echo "target_env=${{ inputs.target_env }}"
echo "skip_dify=${{ inputs.skip_dify }}"
echo "source_sha="
} >> "${GITHUB_OUTPUT}"
fi
- name: Ingest artifact
+4 -4
View File
@@ -36,23 +36,23 @@ concurrency:
jobs:
reconcile:
runs-on: arc-ci
runs-on: ubuntu-latest
env:
KNOWLEDGE_DIFY_BASE_URL: ${{ secrets.KNOWLEDGE_DIFY_BASE_URL }}
KNOWLEDGE_DIFY_API_KEY: ${{ secrets.KNOWLEDGE_DIFY_API_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@v6
- name: Reconcile publish records
run: |
+4 -4
View File
@@ -41,23 +41,23 @@ concurrency:
jobs:
unpublish:
runs-on: arc-ci
runs-on: ubuntu-latest
env:
KNOWLEDGE_DIFY_BASE_URL: ${{ secrets.KNOWLEDGE_DIFY_BASE_URL }}
KNOWLEDGE_DIFY_API_KEY: ${{ secrets.KNOWLEDGE_DIFY_API_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
uses: actions/setup-python@v6
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
uses: astral-sh/setup-uv@v6
- name: Unpublish record
run: |
@@ -1,32 +0,0 @@
name: monthly-rollback-drill
on:
schedule:
# Monthly on day 1, 18:00 UTC
- cron: "0 18 1 * *"
workflow_dispatch:
permissions:
contents: read
jobs:
drill:
if: github.event_name == 'workflow_dispatch' || github.ref == 'refs/heads/main'
runs-on: arc-ci
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
- name: Run rollback drill (dry-run only)
run: bash scripts/ops/run_rollback_drill.sh
-36
View File
@@ -1,36 +0,0 @@
name: nightly-auto-repair
on:
push:
branches-ignore: [gh-pages]
paths-ignore: ['**']
schedule:
# 01:30 Asia/Tokyo (JST) = 16:30 UTC previous day
- cron: "30 16 * * *"
workflow_dispatch:
permissions:
contents: write
issues: write
jobs:
repair:
runs-on: arc-ci
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Run auto-repair loop
run: |
bash scripts/repair/run.sh \
--allow-main \
--check-cmd "bash scripts/ci/run_checks.sh" \
--max-attempts 3 \
--recipes repairs/recipes.yaml \
--issue-repo "${{ github.repository }}"
-108
View File
@@ -1,108 +0,0 @@
name: nightly-evidence
on:
push:
branches-ignore: [gh-pages]
paths-ignore: ['**']
schedule:
# 02:00 Asia/Tokyo (JST) = 17:00 UTC previous day
- cron: "0 17 * * *"
workflow_dispatch:
permissions:
contents: write
concurrency:
group: nightly-evidence-publish
cancel-in-progress: false
jobs:
publish:
runs-on: arc-ci
steps:
- name: Checkout main
uses: actions/checkout@v4
with:
ref: main
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Setup uv
uses: astral-sh/setup-uv@v4
- name: Pgvector static health check
run: bash scripts/ops/check-pgvector.sh
- name: Validate and collect evidence
run: |
uvx --with pyyaml --with jsonschema python scripts/evidence/validate.py
uvx --with pyyaml --with jsonschema python scripts/evidence/collect.py --out evidence/index.json --summary evidence/summary/latest.md
uvx --with pyyaml python scripts/promoter/queue_metrics.py --queue release/queue.yaml --out evidence/metrics/queue-health.json
- name: Build publish payload (atomic staging)
run: |
rm -rf .publish-tmp
mkdir -p .publish-tmp/evidence/summary .publish-tmp/evidence/metrics
cp pages/index.html pages/app.js .publish-tmp/
cp evidence/index.json .publish-tmp/evidence/index.json
cp evidence/summary/latest.md .publish-tmp/evidence/summary/latest.md
cp evidence/metrics/queue-health.json .publish-tmp/evidence/metrics/queue-health.json
python3 - <<'PY'
import json, pathlib
feed = pathlib.Path('.publish-tmp/evidence/index.json')
data = json.loads(feed.read_text(encoding='utf-8'))
if not isinstance(data, list):
raise SystemExit('evidence/index.json must be an array')
print(f'staged records: {len(data)}')
PY
- name: Publish to gh-pages branch
run: |
set -euo pipefail
rm -rf .gh-pages-wt
if git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then
git worktree add --force -B gh-pages .gh-pages-wt origin/gh-pages
else
git worktree add --force -B gh-pages .gh-pages-wt
fi
cd .gh-pages-wt
find . -mindepth 1 -maxdepth 1 ! -name '.git' -exec rm -rf {} +
cp -R ../.publish-tmp/. .
python3 - <<'PY'
import json
from pathlib import Path
index_path = Path("evidence/index.json")
if not index_path.exists():
raise SystemExit("missing evidence/index.json in gh-pages payload")
data = json.loads(index_path.read_text(encoding="utf-8"))
if not isinstance(data, list):
raise SystemExit("evidence/index.json in gh-pages payload must be an array")
print(f"publish records: {len(data)}")
PY
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add -A
extra_tracked="$(git ls-files | grep -Ev '^(index\.html|app\.js|evidence/index\.json|evidence/summary/latest\.md|evidence/metrics/queue-health\.json)$' || true)"
if [ -n "${extra_tracked}" ]; then
echo "Unexpected tracked files in gh-pages branch:"
echo "${extra_tracked}"
exit 1
fi
if git diff --cached --quiet; then
echo "No changes to publish"
exit 0
fi
git commit -m "pages: update evidence feed [skip ci]"
git push origin HEAD:gh-pages
@@ -0,0 +1,33 @@
name: Ops Emergency Follow-up
on:
pull_request:
branches: [main]
types: [closed]
permissions:
contents: read
issues: write
jobs:
create-followup:
if: github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'ops-emergency-rollback')
runs-on: ubuntu-latest
steps:
- name: Create follow-up when emergency PR has no issue link
uses: actions/github-script@v7
with:
script: |
const pr = context.payload.pull_request;
const tracked = /^\s*(Closes|Fixes|Refs)\s*:?\s+.*(#\d+|github\.com\/[^/]+\/[^/]+\/issues\/\d+)/im.test(pr.body || '');
if (tracked) {
core.notice(`Emergency PR #${pr.number} already links a follow-up issue.`);
return;
}
const created = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `[post-emergency] Audit rollback PR #${pr.number}`,
body: `## Owner\n@${pr.user.login}\n\n## Scope\nAudit emergency rollback PR #${pr.number}.\n\n## Evidence\n- PR: ${pr.html_url}\n- Merged commit: ${pr.merge_commit_sha}\n\n## Acceptance\n- [ ] Confirm incident timeline and production impact\n- [ ] Attach validation evidence\n- [ ] Record permanent corrective action\n\n## Rollback\nDocument whether the emergency state remains active or was superseded.`,
});
core.notice(`Created auditable follow-up issue ${created.data.html_url}`);
+11 -9
View File
@@ -5,6 +5,7 @@ GitOps 配置仓库,使用 ArgoCD 管理 Kubernetes 集群。
## Start Here
- 新进程/新项目/老项目统一接入入口:`docs/START-HERE-GITOPS-ONBOARDING.md`
- LJWX 业务部署权威流程:`docs/ljwx-business-deployment-flow.md`
- 自动扫描当前仓库是否已接入:`bash scripts/ops/scan-gitops-context.sh --repo "$PWD"`
## Platform Blueprint
@@ -19,7 +20,8 @@ GitOps 配置仓库,使用 ArgoCD 管理 Kubernetes 集群。
- 运行验证:`bash scripts/verify.sh`
- Harbor 复制:`docs/harbor-replication-ghcr.md`
- Promoter/Argo/Smoke 运维:`docs/ops-runbook.md`
- Controller/Argo 运维:`docs/ops-runbook.md`
- GitOps v2 设计入口:`docs/gitops-v2/README.md`
- 生产集群 Bootstrap(仅生产集群执行):`argocd-apps/02-cluster-prod-bootstrap.yaml`
- 生产镜像拉取凭据自动同步:`cluster-prod/registry-pull-secret-sync-*.yaml`
- 回滚演练:`docs/rollback-drill.md`
@@ -41,14 +43,14 @@ GitOps 配置仓库,使用 ArgoCD 管理 Kubernetes 集群。
- `platform/` - 平台装配、契约、路由、知识治理配置
- `runbooks/` - 平台级操作手册
## 生产发布闭环(当前)
## 生产发布闭环(当前口径)
1. 服务仓 CI push GHCR(不可变 digest)。
2. 本地 Harbor 同步 GHCR;`deploy-promoter` 仅处理 `dev,demo`。
3. smoke 通过后自动给本地 Harbor 打 `prod-*` 标签,并自动 enqueue `env=prod`。
4. 生产 Harbor 仅同步 `prod-*` 标签。
5. 生产集群 `deploy-promoter-prod` 等待生产 Harbor digest 就绪后更新 prod overlay。
6. `registry-pull-secret-sync` 自动把 `default/harbor-registry` 下发到标记 namespace(`registry-sync.ljwx.io/enabled=true`),避免 prod Pod 拉镜像失败。
1. 服务仓在 GitHub / Runner 构建,并把候选镜像 push 到 profile 指定的 candidate registry;当前标准优先 GHCR。
2. 服务仓上传 `ReleaseBundle` artifact,并向 `ljwx-release-state` 发 `repository_dispatch`。
3. `ljwx-release-state` 生成 `Event -> ReleaseBundle -> ReleaseRequest -> DeployResolution`。
4. `deploy-controller-v2` 从 `ljwx-release-state` 拉取 resolution,并作为 `ljwx-deploy` 的唯一写入者更新 overlay。
5. 如目标为 `prod`,controller 按 `release/services*.yaml` 的 `deployImage` 和 resolution 的 `copyRequired` 决定是否复制镜像;`deployImage` 为 GHCR 且 `copyRequired=false` 时不经过本地 Harbor。
6. controller 把 `ReleaseResult` 回写到 `ljwx-release-state`;legacy promoter/smoke CronJob 已 suspend,不再作为默认发布入口。
## 组件
@@ -56,7 +58,7 @@ GitOps 配置仓库,使用 ArgoCD 管理 Kubernetes 集群。
- 支持 Gitea Actions 的弹性 runner
- 自动伸缩: min 1, max 5 (基于 CPU/Memory)
- 支持 Docker build/push
- 适合轻量校验、部署编排和运维任务;不再作为标准重 CI 主力
- Harbor registry: https://harbor.omniverseai.net
#### 配置说明
@@ -5,5 +5,5 @@ resources:
- ../../base
images:
- name: harbor.eu.lingjingwanxiang.cn/ljwx/agent-platform-demo
newName: harbor.eu.lingjingwanxiang.cn/ljwx/agent-platform-demo
digest: sha256:27b0fc823384125e8d3fbe357a0fe3382cb405222a28708889227416aa318c68
newName: ghcr.io/lingjingwanxiang/agent-platform-demo
digest: sha256:4f007ee49dbc5c175eb734d6f81b647f37b591ffc56c737a25ee5c34fa931476
@@ -5,5 +5,5 @@ resources:
- ../../base
images:
- name: ghcr.io/lingjingwanxiang/agent-platform-api
newName: harbor.eu.lingjingwanxiang.cn/ljwx/agent-platform-api
digest: sha256:18f11fa2b9543c30edc95305bee9fd1404d6d67ba712a1b295332ecccf6cf7e3
newName: ghcr.io/lingjingwanxiang/agent-platform-api
digest: sha256:bb9b65cc570b155cf9d2b24020ce8d2fa39400d36f19863fb84b0baa43c80ec2
@@ -0,0 +1,23 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cloudflare-letsencrypt-staging
labels:
app.kubernetes.io/part-of: cert-manager-cloudflare-dns01
annotations:
gitops.ljwx.io/credential-mode: account-api-token
spec:
acme:
email: admin@omniverseai.net
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: cloudflare-letsencrypt-staging-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- omniverseai.net
@@ -0,0 +1,23 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: cloudflare-letsencrypt
labels:
app.kubernetes.io/part-of: cert-manager-cloudflare-dns01
annotations:
gitops.ljwx.io/credential-mode: account-api-token
spec:
acme:
email: admin@omniverseai.net
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: cloudflare-letsencrypt-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- omniverseai.net
@@ -3,7 +3,7 @@ kind: ClusterIssuer
metadata:
name: dnspod-letsencrypt-staging
annotations:
gitops.ljwx.io/issuer-implementation: http01-traefik
gitops.ljwx.io/issuer-implementation: http01-nginx
spec:
acme:
email: platform@lingjingwanxiang.cn
@@ -13,4 +13,4 @@ spec:
solvers:
- http01:
ingress:
ingressClassName: traefik
ingressClassName: nginx
@@ -3,7 +3,7 @@ kind: ClusterIssuer
metadata:
name: dnspod-letsencrypt
annotations:
gitops.ljwx.io/issuer-implementation: http01-traefik
gitops.ljwx.io/issuer-implementation: http01-nginx
spec:
acme:
email: platform@lingjingwanxiang.cn
@@ -13,4 +13,4 @@ spec:
solvers:
- http01:
ingress:
ingressClassName: traefik
ingressClassName: nginx
@@ -0,0 +1,24 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
labels:
app.kubernetes.io/part-of: cert-manager-cloudflare-dns01
baseline.macos-k3/compatibility-alias: "true"
annotations:
gitops.ljwx.io/credential-mode: account-api-token
spec:
acme:
email: admin@omniverseai.net
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- omniverseai.net
@@ -0,0 +1,24 @@
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
labels:
app.kubernetes.io/part-of: cert-manager-cloudflare-dns01
baseline.macos-k3/compatibility-alias: "true"
annotations:
gitops.ljwx.io/credential-mode: account-api-token
spec:
acme:
email: admin@omniverseai.net
server: https://acme-staging-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-staging-account-key
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: cloudflare-api-token
key: api-token
selector:
dnsZones:
- omniverseai.net
@@ -1,5 +1,9 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- clusterissuer-cloudflare-letsencrypt.yaml
- clusterissuer-cloudflare-letsencrypt-staging.yaml
- clusterissuer-letsencrypt-prod.yaml
- clusterissuer-letsencrypt-staging.yaml
- clusterissuer-dnspod-letsencrypt.yaml
- clusterissuer-dnspod-letsencrypt-staging.yaml
@@ -5,8 +5,8 @@ resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx/ljwx-bookstore
newName: ghcr.io/brunogaosz/ljwx-bookstore
digest: sha256:87d09e8ff42a834699e61b7ddfdcc4d19184a42e1653e9d1d7ca2c36eb70ee05
newName: ghcr.io/lingjingwanxiang/ljwx-bookstore
digest: sha256:4e74338de6dbbe574d1050250e435aa47c2760ccb32bfb214ae8a95f40e57a1f
replicas:
- name: ljwx-bookstore
count: 1
+5 -1
View File
@@ -14,7 +14,7 @@ data:
NEXT_AUTH_SSO_SESSION_STRATEGY: "jwt"
NEXT_AUTH_SSO_PROVIDERS: ""
AUTH_TRUST_HOST: "true"
DEFAULT_AGENT_CONFIG: 'model=lingjingwanxiang:32b;provider=ollama;systemRole="默认使用中文回答,除非用户明确要求使用其他语言。"'
DEFAULT_AGENT_CONFIG: 'model=local-rag-default;provider=openai;systemRole="默认使用中文回答,除非用户明确要求使用其他语言。"'
EMAIL_ENABLED: "false"
@@ -26,6 +26,10 @@ data:
MIDDLEWARE_REWRITE_THROUGH_LOCAL: "1"
ENABLED_OPENAI: "1"
OPENAI_API_KEY: "local"
OPENAI_PROXY_URL: "http://mlx-qwen:18088/v1"
OPENAI_MODEL_LIST: "-all,+local-rag-default,+local-rag-quality,+local-review"
ENABLED_OLLAMA: "0"
OPENCLAW_GATEWAY_URL: "https://openclaw.lingjingwanxiang.cn/v1"
OPENCLAW_GATEWAY_ORIGIN: "https://openclaw.lingjingwanxiang.cn"
+5 -6
View File
@@ -3,21 +3,20 @@ kind: Ingress
metadata:
name: ljwx-chat
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: dnspod-letsencrypt
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
traefik.ingress.kubernetes.io/router.tls: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: traefik
tls:
- hosts:
- chat.lingjingwanxiang.cn
secretName: chat-lingjingwanxiang-cn-tls
- chat.omniverseai.net
secretName: chat-omniverseai-net-tls
rules:
- host: chat.lingjingwanxiang.cn
- host: chat.omniverseai.net
http:
paths:
- path: /
@@ -11,5 +11,5 @@ patchesStrategicMerge:
- deployment-routing-patch.yaml
images:
- name: ghcr.io/brunogao/ljwx-chat
newName: ghcr.io/brunogao/ljwx-chat
newTag: sha-976fea8
newName: ghcr.io/lingjingwanxiang/ljwx-chat
digest: sha256:f1acba4b77166a3241941da1b90e6c1309fd7e2a3c8acc8370732940bfe2965b
@@ -5,5 +5,5 @@ resources:
- ../../base
images:
- name: ghcr.io/brunogao/ljwx-core-api
newName: ghcr.io/brunogao/ljwx-core-api
digest: sha256:b7a9760defea2b10eabf468b20342ef6775185e147cc384b37fe7dc331b0c5cc
newName: ghcr.io/lingjingwanxiang/ljwx-core-api
digest: sha256:299a6f33744356c0ec6ee32da60a482e261f2c53e45e880706812ae45ed7ca6c
+2
View File
@@ -21,6 +21,8 @@ spec:
app: ljwx-dify-api
template:
metadata:
annotations:
ljwx.omniverseai.net/config-revision: local-llm-alias-20260517
labels:
app: ljwx-dify-api
spec:
+46
View File
@@ -0,0 +1,46 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-dify-brand-assets
data:
lingjing-icon.svg: |
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="lingjing-icon-gradient" x1="8" y1="7" x2="40" y2="41" gradientUnits="userSpaceOnUse">
<stop stop-color="#667EEA"/>
<stop offset="1" stop-color="#06B6D4"/>
</linearGradient>
</defs>
<rect x="4" y="4" width="40" height="40" rx="10" fill="#111827"/>
<path d="M24 8L36 15V29L24 36L12 29V15L24 8Z" fill="url(#lingjing-icon-gradient)"/>
<path d="M24 14V30M17.5 18H30.5M17.5 24H30.5" stroke="white" stroke-width="2" stroke-linecap="round"/>
<circle cx="32" cy="14" r="2.5" fill="#22D3EE"/>
</svg>
lingjing-logo-light.svg: |
<svg width="174" height="32" viewBox="0 0 174 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="lingjing-logo-light-gradient" x1="3" y1="3" x2="29" y2="29" gradientUnits="userSpaceOnUse">
<stop stop-color="#667EEA"/>
<stop offset="1" stop-color="#06B6D4"/>
</linearGradient>
</defs>
<rect x="1" y="1" width="30" height="30" rx="8" fill="#111827"/>
<path d="M16 5L25 10.5V21.5L16 27L7 21.5V10.5L16 5Z" fill="url(#lingjing-logo-light-gradient)"/>
<path d="M16 10V22M11 13.5H21M11 18.5H21" stroke="white" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="22.5" cy="9.5" r="1.8" fill="#22D3EE"/>
<text x="42" y="22" fill="#111827" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif" font-size="18" font-weight="700">灵境万象</text>
</svg>
lingjing-logo-dark.svg: |
<svg width="174" height="32" viewBox="0 0 174 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="lingjing-logo-dark-gradient" x1="3" y1="3" x2="29" y2="29" gradientUnits="userSpaceOnUse">
<stop stop-color="#8B9BFF"/>
<stop offset="1" stop-color="#22D3EE"/>
</linearGradient>
</defs>
<rect x="1" y="1" width="30" height="30" rx="8" fill="#1F2937"/>
<path d="M16 5L25 10.5V21.5L16 27L7 21.5V10.5L16 5Z" fill="url(#lingjing-logo-dark-gradient)"/>
<path d="M16 10V22M11 13.5H21M11 18.5H21" stroke="white" stroke-width="1.6" stroke-linecap="round"/>
<circle cx="22.5" cy="9.5" r="1.8" fill="#67E8F9"/>
<text x="42" y="22" fill="#F9FAFB" font-family="-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Microsoft YaHei',sans-serif" font-size="18" font-weight="700">灵境万象</text>
</svg>
+9 -2
View File
@@ -38,5 +38,12 @@ data:
DIFY_CONSOLE_API_BASE: "http://api:5001/console/api"
DIFY_INIT_WAIT_SECONDS: "300"
DIFY_INIT_REQUEST_TIMEOUT_SECONDS: "15"
DIFY_DEFAULT_LLM_PROVIDER: "anthropic"
DIFY_DEFAULT_LLM_MODEL: "claude-sonnet-4-6"
DIFY_DEFAULT_LLM_PROVIDER: "openai_api_compatible"
DIFY_DEFAULT_LLM_MODEL: "local-rag-default"
DIFY_DEFAULT_LLM_CONTEXT_SIZE: "32768"
DEFAULT_MODEL_PROVIDER: "openai_api_compatible"
DEFAULT_MODEL_NAME: "local-rag-default"
APP_DEFAULT_LLM_PROVIDER: "openai_api_compatible"
APP_DEFAULT_LLM_MODEL: "local-rag-default"
OPENAI_API_BASE: "http://mlx-qwen:18088/v1"
OPENAI_API_KEY: "local"
+5 -7
View File
@@ -3,19 +3,17 @@ kind: Ingress
metadata:
name: ljwx-dify
annotations:
cert-manager.io/cluster-issuer: dnspod-letsencrypt
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
traefik.ingress.kubernetes.io/router.tls: "true"
cert-manager.io/cluster-issuer: cloudflare-letsencrypt
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/websocket-services: "api"
spec:
ingressClassName: traefik
ingressClassName: nginx
tls:
- hosts:
- dify.lingjingwanxiang.cn
secretName: dify-lingjingwanxiang-cn-tls
- dify.omniverseai.net
secretName: dify-omniverseai-net-tls
rules:
- host: dify.lingjingwanxiang.cn
- host: dify.omniverseai.net
http:
paths:
- path: /api
@@ -56,8 +56,11 @@ spec:
PROVIDER_HINT = os.getenv("DIFY_DEFAULT_LLM_PROVIDER", "anthropic").strip() or "anthropic"
DEFAULT_MODEL = os.getenv("DIFY_DEFAULT_LLM_MODEL", "claude-sonnet-4-6").strip()
DEFAULT_CONTEXT_SIZE = os.getenv("DIFY_DEFAULT_LLM_CONTEXT_SIZE", "32768").strip() or "32768"
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "").strip()
ANTHROPIC_BASE_URL = os.getenv("ANTHROPIC_BASE_URL", "https://api.anthropic.com").strip()
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "local").strip() or "local"
OPENAI_API_BASE = os.getenv("OPENAI_API_BASE", "").strip().rstrip("/")
DB_HOST = os.getenv("DB_HOST", "").strip()
DB_PORT = int(os.getenv("DB_PORT", "5432"))
@@ -77,8 +80,10 @@ spec:
fail("缺少 DIFY_ADMIN_PASSWORD,无法登录 Dify 控制台。")
if not DEFAULT_MODEL:
fail("缺少 DIFY_DEFAULT_LLM_MODEL,无法设置默认模型。")
if not ANTHROPIC_API_KEY:
if "anthropic" in PROVIDER_HINT.lower() and not ANTHROPIC_API_KEY:
fail("缺少 ANTHROPIC_API_KEY,无法配置 Anthropic Provider。")
if "openai_api_compatible" in PROVIDER_HINT.lower() and not OPENAI_API_BASE:
fail("缺少 OPENAI_API_BASE,无法配置 OpenAI-compatible Provider。")
cookie_jar = CookieJar()
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie_jar))
@@ -451,6 +456,68 @@ spec:
fail("Anthropic provider 在超时时间内仍不可用。")
def ensure_openai_api_compatible_plugin_ready() -> None:
plugin_id = "langgenius/openai_api_compatible"
existing_providers, provider_error = try_list_llm_providers()
if existing_providers is not None and find_provider(existing_providers, PROVIDER_HINT) is not None:
print("[INFO] OpenAI-compatible provider 已存在,跳过插件安装检查。")
return
if provider_error:
print(f"[WARN] LLM provider 列表暂不可用,继续执行插件探测: {provider_error}")
_, latest_body, _ = request_json(
"POST",
"/workspaces/current/plugin/list/latest-versions",
payload={"plugin_ids": [plugin_id]},
expected_status={200},
)
latest_versions = latest_body.get("versions", {}) if isinstance(latest_body, dict) else {}
latest_info = latest_versions.get(plugin_id) if isinstance(latest_versions, dict) else None
if not isinstance(latest_info, dict):
fail("无法获取 OpenAI-compatible 插件最新版本信息。")
latest_unique = str(latest_info.get("unique_identifier", "")).strip()
if not latest_unique:
fail("OpenAI-compatible 插件最新 unique_identifier 为空。")
print(f"[INFO] 安装或确认 OpenAI-compatible 插件: {latest_unique}")
install_status, install_body, install_text = request_json(
"POST",
"/workspaces/current/plugin/install/marketplace",
payload={"plugin_unique_identifiers": [latest_unique]},
expected_status={200, 400, 409, 500},
)
task_id = install_body.get("task_id") if isinstance(install_body, dict) else None
all_installed = bool(install_body.get("all_installed")) if isinstance(install_body, dict) else False
if install_status == 200 and task_id:
wait_plugin_task(str(task_id))
elif install_status == 200 and all_installed:
print("[INFO] OpenAI-compatible 插件已经安装。")
elif install_status == 200:
print("[WARN] 安装 OpenAI-compatible 插件返回成功但没有 task_id,继续等待 provider 就绪。")
elif is_ignorable_plugin_response(install_status, install_body, install_text):
print("[WARN] OpenAI-compatible 插件已存在或处于可忽略状态,继续等待 provider 就绪。")
else:
fail(
f"安装 OpenAI-compatible 插件失败,status={install_status},"
f"{_error_text(install_body, install_text)}"
)
deadline = time.time() + WAIT_SECONDS
while time.time() < deadline:
providers, provider_error = try_list_llm_providers()
if providers is not None and find_provider(providers, PROVIDER_HINT) is not None:
print("[INFO] OpenAI-compatible provider 已可用。")
return
if provider_error:
print(f"[INFO] 等待 OpenAI-compatible provider 注册到模型列表... {provider_error}")
else:
print("[INFO] 等待 OpenAI-compatible provider 注册到模型列表...")
time.sleep(3)
fail("OpenAI-compatible provider 在超时时间内仍不可用。")
def build_provider_credentials(provider_item: dict[str, Any]) -> dict[str, str]:
schema = provider_item.get("provider_credential_schema") or {}
forms = schema.get("credential_form_schemas") if isinstance(schema, dict) else None
@@ -695,6 +762,214 @@ spec:
print("[WARN] 设置 provider preferred_provider_type 返回 400,已忽略。")
def build_openai_api_compatible_model_credentials() -> dict[str, str]:
return {
"display_name": DEFAULT_MODEL,
"api_key": OPENAI_API_KEY,
"endpoint_url": OPENAI_API_BASE,
"endpoint_model_name": DEFAULT_MODEL,
"mode": "chat",
"context_size": DEFAULT_CONTEXT_SIZE,
}
def upsert_custom_model_via_db(
provider_name: str,
model_name: str,
credentials: dict[str, str],
) -> None:
try:
import psycopg2
except Exception as exc: # noqa: BLE001
fail(f"未安装 psycopg2,无法执行数据库回退写入 custom model: {exc}")
required = {
"DB_HOST": DB_HOST,
"DB_DATABASE": DB_DATABASE,
"DB_USERNAME": DB_USERNAME,
"DB_PASSWORD": DB_PASSWORD,
}
missing = [k for k, v in required.items() if not v]
if missing:
fail(f"数据库回退写入 custom model 缺少配置: {missing}")
try:
conn = psycopg2.connect(
host=DB_HOST,
port=DB_PORT,
dbname=DB_DATABASE,
user=DB_USERNAME,
password=DB_PASSWORD,
connect_timeout=REQUEST_TIMEOUT_SECONDS,
)
except Exception as exc: # noqa: BLE001
fail(f"连接数据库失败,无法回退写入 custom model: {exc}")
try:
with conn:
with conn.cursor() as cur:
cur.execute(
"""
select taj.tenant_id
from tenant_account_joins taj
join accounts a on a.id = taj.account_id
where lower(a.email) = lower(%s)
order by taj.created_at asc
limit 1
""",
(ADMIN_EMAIL,),
)
row = cur.fetchone()
if not row or not row[0]:
fail(f"未找到管理员账号 {ADMIN_EMAIL} 关联的 tenant_id。")
tenant_id = str(row[0])
credential_id = str(uuid.uuid4())
provider_model_id = str(uuid.uuid4())
encrypted_config = json.dumps(credentials, ensure_ascii=False)
cur.execute(
"""
delete from provider_model_credentials
where tenant_id = %s
and provider_name = %s
and model_name = %s
and model_type = 'text-generation'
""",
(tenant_id, provider_name, model_name),
)
cur.execute(
"""
delete from provider_models
where tenant_id = %s
and provider_name = %s
and model_name = %s
and model_type = 'text-generation'
""",
(tenant_id, provider_name, model_name),
)
cur.execute(
"""
insert into provider_model_credentials
(id, tenant_id, provider_name, model_name, model_type,
credential_name, encrypted_config, created_at, updated_at)
values (%s, %s, %s, %s, 'text-generation',
%s, %s, now(), now())
""",
(
credential_id,
tenant_id,
provider_name,
model_name,
model_name,
encrypted_config,
),
)
cur.execute(
"""
insert into provider_models
(id, tenant_id, provider_name, model_name, model_type,
credential_id, is_valid, created_at, updated_at)
values (%s, %s, %s, %s, 'text-generation',
%s, true, now(), now())
""",
(
provider_model_id,
tenant_id,
provider_name,
model_name,
credential_id,
),
)
cur.execute(
"""
delete from tenant_default_models
where tenant_id = %s and model_type in ('text-generation', 'llm')
""",
(tenant_id,),
)
for default_model_type in ("text-generation", "llm"):
cur.execute(
"""
insert into tenant_default_models
(id, tenant_id, provider_name, model_name, model_type,
created_at, updated_at)
values (%s, %s, %s, %s, %s, now(), now())
""",
(
str(uuid.uuid4()),
tenant_id,
provider_name,
model_name,
default_model_type,
),
)
print("[WARN] custom model API 校验失败,已通过数据库回退写入 MLX 模型。")
except Exception as exc: # noqa: BLE001
fail(f"数据库回退写入 custom model 失败: {exc}")
finally:
conn.close()
def ensure_custom_model_credentials(
provider_name: str,
model_name: str,
credentials: dict[str, str],
) -> None:
quoted_provider = quote_provider_path(provider_name)
get_status, get_body, _ = request_json(
"GET",
f"/workspaces/current/model-providers/{quoted_provider}/models/credentials"
f"?model={urllib.parse.quote(model_name)}&model_type=llm&config_from=custom-model",
expected_status={200, 400, 500},
)
current_credential_id = None
if get_status == 200 and isinstance(get_body, dict):
current_credential_id = get_body.get("current_credential_id")
if current_credential_id:
status, body, text = request_json(
"PUT",
f"/workspaces/current/model-providers/{quoted_provider}/models/credentials",
payload={
"credential_id": current_credential_id,
"model": model_name,
"model_type": "llm",
"credentials": credentials,
"name": model_name,
},
expected_status={200, 400, 500},
)
if status == 200:
print("[INFO] 已更新 OpenAI-compatible custom model 凭据。")
return
print(
f"[WARN] custom model 凭据更新校验失败,status={status},"
f"{_error_text(body, text)}"
)
else:
status, body, text = request_json(
"POST",
f"/workspaces/current/model-providers/{quoted_provider}/models/credentials",
payload={
"model": model_name,
"model_type": "llm",
"credentials": credentials,
"name": model_name,
},
expected_status={201, 400, 500},
)
if status == 201:
print("[INFO] 已创建 OpenAI-compatible custom model 凭据。")
return
print(
f"[WARN] custom model 凭据创建校验失败,status={status},"
f"{_error_text(body, text)}"
)
upsert_custom_model_via_db(provider_name, model_name, credentials)
def set_default_model(provider_name: str, model_name: str) -> None:
quoted_provider = quote_provider_path(provider_name)
_, model_body, model_text = request_json(
@@ -766,15 +1041,27 @@ spec:
ensure_setup()
login_console()
if "anthropic" in PROVIDER_HINT.lower():
provider_hint_lower = PROVIDER_HINT.lower()
if "anthropic" in provider_hint_lower:
ensure_anthropic_plugin_ready()
elif "openai_api_compatible" in provider_hint_lower:
ensure_openai_api_compatible_plugin_ready()
providers = list_llm_providers()
provider_item = pick_provider(providers, PROVIDER_HINT)
selected_provider_name = str(provider_item.get("provider", PROVIDER_HINT))
provider_credentials = build_provider_credentials(provider_item)
ensure_provider_credentials(selected_provider_name, provider_credentials)
if "openai_api_compatible" in provider_hint_lower:
custom_model_credentials = build_openai_api_compatible_model_credentials()
ensure_custom_model_credentials(
selected_provider_name,
DEFAULT_MODEL,
custom_model_credentials,
)
else:
provider_credentials = build_provider_credentials(provider_item)
ensure_provider_credentials(selected_provider_name, provider_credentials)
set_default_model(selected_provider_name, DEFAULT_MODEL)
print("[INFO] Dify 默认 LLM 自动化配置完成。")
PY
+1
View File
@@ -2,6 +2,7 @@ apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- brand-assets.yaml
- configmap.yaml
- job-upgrade-db.yaml
- job-configure-default-llm.yaml
+86 -4
View File
@@ -26,15 +26,68 @@ spec:
spec:
imagePullSecrets:
- name: regcred
initContainers:
- name: migrate-plugin-packages
image: docker.io/langgenius/dify-plugin-daemon:0.5.2-local
command:
- /bin/sh
- -lc
- |
set -eu
python3 - <<'PY'
import zipfile
from pathlib import Path
root = Path("/app/storage/plugin")
package_root = Path("/app/storage/plugin_packages")
package_root.mkdir(parents=True, exist_ok=True)
skip_dirs = {".venv", "__pycache__", ".git", ".pytest_cache"}
migrated = 0
for publisher_dir in root.iterdir() if root.exists() else []:
if not publisher_dir.is_dir():
continue
for source in publisher_dir.iterdir():
if not source.is_dir() or "-" not in source.name or "@" not in source.name:
continue
plugin_name, version_hash = source.name.rsplit("-", 1)
unique_name = f"{plugin_name}:{version_hash}"
targets = [
publisher_dir / unique_name,
package_root / publisher_dir.name / unique_name,
]
for target in targets:
target.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(target, "w", zipfile.ZIP_DEFLATED) as archive:
for path in source.rglob("*"):
relative = path.relative_to(source)
if path.is_dir() or any(part in skip_dirs for part in relative.parts):
continue
archive.write(path, relative.as_posix())
migrated += 1
print(f"migrated legacy plugin directories: {migrated}")
PY
volumeMounts:
- name: plugins
mountPath: /app/storage/plugin
- name: plugin-packages
mountPath: /app/storage/plugin_packages
containers:
- name: plugin-daemon
image: langgenius/dify-plugin-daemon:0.5.1-local
image: docker.io/langgenius/dify-plugin-daemon:0.5.2-local
ports:
- containerPort: 5002
name: http
env:
- name: PLUGIN_DAEMON_PORT
value: "5002"
- name: SERVER_PORT
value: "5002"
- name: MAX_PLUGIN_PACKAGE_SIZE
value: "52428800"
- name: PYTHON_ENV_INIT_TIMEOUT
value: "120"
- name: PLUGIN_MAX_EXECUTION_TIMEOUT
value: "600"
- name: REDIS_HOST
value: "redis-lb.infra.svc.cluster.local"
- name: REDIS_PORT
@@ -77,11 +130,27 @@ spec:
name: ljwx-dify-secrets
key: DIFY_INNER_API_KEY
- name: PLUGIN_REMOTE_INSTALLING_HOST
value: "https://dify.lingjingwanxiang.cn"
value: "0.0.0.0"
- name: PLUGIN_REMOTE_INSTALLING_PORT
value: "443"
value: "5003"
- name: PLUGIN_STORAGE_TYPE
value: "local"
- name: PLUGIN_STORAGE_LOCAL_ROOT
value: "/app/storage"
- name: PLUGIN_WORKING_PATH
value: "/app/plugins"
value: "/app/storage/cwd"
- name: PLUGIN_INSTALLED_PATH
value: "plugin"
- name: PLUGIN_PACKAGE_CACHE_PATH
value: "plugin_packages"
- name: PLUGIN_MEDIA_CACHE_PATH
value: "assets"
- name: UV_HTTP_TIMEOUT
value: "300"
- name: UV_DEFAULT_INDEX
value: "https://mirrors.ustc.edu.cn/pypi/simple"
- name: UV_INDEX_URL
value: "https://mirrors.ustc.edu.cn/pypi/simple"
envFrom:
- configMapRef:
name: ljwx-dify-config
@@ -90,6 +159,14 @@ spec:
volumeMounts:
- name: plugins
mountPath: /app/plugins
- name: plugins
mountPath: /app/plugin
- name: plugins
mountPath: /app/storage/plugin
- name: plugin-cwd
mountPath: /app/storage/cwd
- name: plugin-packages
mountPath: /app/storage/plugin_packages
resources:
requests:
memory: "256Mi"
@@ -109,4 +186,9 @@ spec:
periodSeconds: 5
volumes:
- name: plugins
persistentVolumeClaim:
claimName: ljwx-dify-plugins
- name: plugin-cwd
emptyDir: {}
- name: plugin-packages
emptyDir: {}
+11
View File
@@ -8,3 +8,14 @@ spec:
resources:
requests:
storage: 20Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ljwx-dify-plugins
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
+17
View File
@@ -36,6 +36,19 @@ spec:
envFrom:
- configMapRef:
name: ljwx-dify-config
volumeMounts:
- name: brand-assets
mountPath: /app/web/public/logo/lingjing-icon.svg
subPath: lingjing-icon.svg
readOnly: true
- name: brand-assets
mountPath: /app/web/public/logo/lingjing-logo-light.svg
subPath: lingjing-logo-light.svg
readOnly: true
- name: brand-assets
mountPath: /app/web/public/logo/lingjing-logo-dark.svg
subPath: lingjing-logo-dark.svg
readOnly: true
resources:
requests:
memory: "512Mi"
@@ -55,3 +68,7 @@ spec:
port: http
initialDelaySeconds: 10
periodSeconds: 5
volumes:
- name: brand-assets
configMap:
name: ljwx-dify-brand-assets
+2
View File
@@ -9,6 +9,8 @@ spec:
app: ljwx-dify-worker
template:
metadata:
annotations:
ljwx.omniverseai.net/config-revision: local-llm-alias-20260517
labels:
app: ljwx-dify-worker
spec:
@@ -3,12 +3,13 @@ kind: Kustomization
namespace: ljwx-dify-dev
resources:
- ../../base
- mlx-qwen.yaml
images:
- name: ghcr.io/brunogao/ljwx-dify-api
newName: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-dify-api
digest: sha256:508adb624b9199170e622120c5c5afae6a387a21e55ff45e3fb0eb26f3b89811
newName: ghcr.io/lingjingwanxiang/ljwx-dify-api
digest: sha256:0c4c4cf61fb29dc2dcae8a53478f23f260838cc0a9e61b860ef8ecee56215154
- name: ghcr.io/brunogao/ljwx-dify-web
newName: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-dify-web
digest: sha256:15cd5c698296c22d8536dca5206a6b67d5f556f824db3e2208b581ed095378d2
newName: ghcr.io/lingjingwanxiang/ljwx-dify-web
digest: sha256:c5634c14f3c485c8ef22f03f51564c29d3558bc505b798f29585138c5b189b94
- name: qdrant/qdrant
newTag: v1.12.6
@@ -0,0 +1,33 @@
apiVersion: v1
kind: Service
metadata:
name: mlx-qwen
labels:
app.kubernetes.io/name: mlx-qwen
app.kubernetes.io/part-of: ljwx-chat-suite
spec:
type: ClusterIP
ports:
- name: http
port: 18088
targetPort: 18088
protocol: TCP
---
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
name: mlx-qwen
labels:
kubernetes.io/service-name: mlx-qwen
app.kubernetes.io/name: mlx-qwen
app.kubernetes.io/part-of: ljwx-chat-suite
addressType: IPv4
ports:
- name: http
port: 18088
protocol: TCP
endpoints:
- addresses:
- 192.168.1.83
conditions:
ready: true
@@ -18,11 +18,11 @@
## Public endpoint
- ingress profile: `traefik-letsencrypt-public`
- host: `https://dify.lingjingwanxiang.cn`
- ingress class: `traefik`
- cluster issuer: `dnspod-letsencrypt`
- tls secret: `dify-lingjingwanxiang-cn-tls`
- ingress profile: `nginx-cloudflare-public`
- host: `https://dify.omniverseai.net`
- ingress class: `nginx`
- cluster issuer: `cloudflare-letsencrypt`
- tls secret: `dify-omniverseai-net-tls`
- ingress artifact generation: `disabled (managed by existing manifests)`
## Bootstrap commands
@@ -2,12 +2,12 @@ service: ljwx-dify
environment: dev
namespace: ljwx-dify-dev
cluster_bootstrap: true
ingress_profile: traefik-letsencrypt-public
public_host: dify.lingjingwanxiang.cn
ingress_profile: nginx-cloudflare-public
public_host: dify.omniverseai.net
generate_ingress: false
ingress_class_name: traefik
cluster_issuer: dnspod-letsencrypt
tls_secret_name: dify-lingjingwanxiang-cn-tls
ingress_class_name: nginx
cluster_issuer: cloudflare-letsencrypt
tls_secret_name: dify-omniverseai-net-tls
namespace_profile: dev-default
registry_profile: ghcr-and-harbor
runtime_secret_profile: app-runtime
@@ -0,0 +1,70 @@
apiVersion: v1
kind: Secret
metadata:
name: alertmanager-monitoring-kube-prometheus-alertmanager
labels:
app.kubernetes.io/name: alertmanager
app.kubernetes.io/component: configuration
type: Opaque
stringData:
alertmanager.yaml: |
global:
resolve_timeout: 5m
inhibit_rules:
- source_matchers:
- severity="critical"
target_matchers:
- severity="warning"
equal:
- alertname
- cluster
- service
receivers:
- name: "null"
- name: critical-notifications
webhook_configs:
- url: http://ljwx-health-alert-relay.openclaw.svc.cluster.local:8080/alerts
send_resolved: true
max_alerts: 50
- name: warning-notifications
webhook_configs:
- url: http://ljwx-health-alert-relay.openclaw.svc.cluster.local:8080/alerts
send_resolved: true
max_alerts: 50
route:
receiver: "null"
group_by:
- alertname
- cluster
- service
- severity
group_wait: 30s
group_interval: 5m
repeat_interval: 12h
routes:
- matchers:
- alertname="Watchdog"
receiver: "null"
- matchers:
- alertname="InfoInhibitor"
receiver: "null"
- matchers:
- alertname=~"KubeSchedulerDown|KubeProxyDown|KubeControllerManagerDown"
receiver: "null"
- matchers:
- severity="critical"
receiver: critical-notifications
group_wait: 10s
group_interval: 30s
repeat_interval: 1h
- matchers:
- severity="warning"
receiver: warning-notifications
group_interval: 30s
repeat_interval: 4h
templates:
- /etc/alertmanager/config/*.tmpl
@@ -0,0 +1,12 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: observability
resources:
- alertmanager-config.yaml
- prometheusrule.yaml
labels:
- includeSelectors: false
includeTemplates: true
pairs:
app.kubernetes.io/part-of: ljwx-health-operations
app.kubernetes.io/managed-by: argocd
@@ -0,0 +1,65 @@
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: ljwx-health-operations
labels:
release: monitoring
spec:
groups:
- name: ljwx_health_notification_delivery
interval: 30s
rules:
- alert: LjwxHealthAlertRelayUnavailable
expr: kube_deployment_status_replicas_available{namespace="openclaw",deployment="ljwx-health-alert-relay"} < 1
for: 2m
labels:
severity: critical
category: notifications
annotations:
summary: "LJWX Health alert relay is unavailable"
description: "No alert relay replica has been available for two minutes; Feishu notifications cannot be delivered."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/alert-delivery.md"
- alert: LjwxHealthAlertRelayDeliveryFailure
expr: sum(increase(ljwx_alert_relay_deliveries_total{result="failure"}[15m])) > 0
for: 1m
labels:
severity: critical
category: notifications
annotations:
summary: "LJWX Health alert delivery failed"
description: "At least one Feishu delivery attempt failed in the last 15 minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/alert-delivery.md"
- alert: LjwxHealthAlertRelayMetricsMissing
expr: absent(up{namespace="openclaw",service="ljwx-health-alert-relay"})
for: 5m
labels:
severity: warning
category: observability
annotations:
summary: "LJWX Health alert relay metrics are missing"
description: "Prometheus has not discovered alert relay metrics for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/alert-delivery.md"
- alert: LjwxHealthAlertRelayEscalationUnconfigured
expr: ljwx_alert_relay_escalation_configured == 0
for: 5m
labels:
severity: warning
category: notifications
annotations:
summary: "LJWX Health alert escalation target is not configured"
description: "The primary receiver works, but SLA-expired unread alerts cannot reach the backup on-call target."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/alert-delivery.md"
- alert: LjwxHealthAlertRelayEscalationFailure
expr: sum(increase(ljwx_alert_relay_escalations_total{result="failure"}[15m])) > 0
for: 1m
labels:
severity: critical
category: notifications
annotations:
summary: "LJWX Health alert escalation failed"
description: "At least one unread alert exceeded its acknowledgement SLA but could not be delivered to the backup on-call target."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/alert-delivery.md"
+64
View File
@@ -0,0 +1,64 @@
# LJWX Health Secret Management
`ljwx-health-secret` is a runtime-only Kubernetes Secret. Values must never be committed to this repository, printed in CI, or copied into issue evidence.
## MinIO credentials
`OSS_ENDPOINT` targets `minio.infra.svc.cluster.local:9000`, so the runtime
credential must match `infra/minio-credentials`; placeholder defaults are not
valid production configuration. Synchronize and verify the two keys before a
rollout or after MinIO credential rotation:
```bash
apps/ljwx-health/base/sync-minio-credentials.sh
```
The script copies the base64 Secret fields without decoding or printing them,
merge-patches only `OSS_ACCESS_KEY` and `OSS_SECRET_KEY`, verifies exact parity,
and removes `kubectl.kubernetes.io/last-applied-configuration` from runtime
Secrets because that annotation contains plaintext-equivalent values. It does
not restart workloads; advance the pod-template revision through GitOps, wait
for both Boot replicas, then exercise an authenticated object write/read.
`create-secret.sh` also reads the same in-cluster source when accessible and no
explicit OSS environment values are supplied. It no longer supplies a
`minioadmin` fallback.
## Restricted PostgreSQL runtime role
`ljwx-boot` connects as `ljwx_app` and reads `DB_APP_PASSWORD`. Schema hooks remain isolated on `DB_MIGRATION_USER=postgres` with the existing `DB_PASSWORD`; do not point runtime traffic at that administrator credential.
Provision or rotate the application password from a protected operator shell before the GitOps cutover:
```bash
apps/ljwx-health/base/provision-db-app-password.sh
```
The script generates a value when `DB_APP_PASSWORD` is absent, sends the role DDL over stdin, verifies TCP authentication plus `NOSUPERUSER/NOBYPASSRLS`, and merges only the new Secret key. It never prints the password. Rotation does not restart workloads.
Cutover order: provision the key, reconcile the GitOps RLS policy bundle, run a restricted-role canary, merge the runtime identity change, wait for both Boot replicas, then verify `pg_stat_activity` contains `ljwx_app` and no Boot-owned `postgres` JDBC sessions. Roll back by reverting `DB_USER`/`SPRING_DATASOURCE_USERNAME` to `postgres`, both Deployment password selectors to `DB_PASSWORD`, and advancing the pod-template revision. Policy reconciliation is forward-compatible with either role and does not need to be undone.
## Sa-Token JWT signing key
The `ljwx-boot` Deployment reads key `SA_TOKEN_JWT_SECRET` as environment variable `SA_TOKEN_JWT_SECRET`. The application requires at least 32 characters and fails closed in the `k8s` profile when the key is absent.
Rotate only this key from a protected operator shell:
```bash
apps/ljwx-health/base/rotate-sa-token-jwt-secret.sh
```
The script generates 32 random bytes, patches only the named key, withholds the value from output, and preserves every other key in the Secret. A caller may supply a value through the process environment, but must avoid shell history and CI output.
Rotation alone does not restart workloads. Merge the GitOps manifest and application changes, allow Argo CD to roll out `ljwx-boot`, then verify both replicas. Every token or session signed with the old key becomes invalid; Admin, Phone, Watch proxy, and Bigscreen users must sign in again.
Use the application repository's redaction-safe guard for post-rollout verification:
```bash
kubectl logs -n ljwx-health deployment/ljwx-boot --all-pods=true --since=30m \
| python3 scripts/testops/auth_secret_guard.py logs --input -
```
`ljwx-health-config` must keep `LOGGING_LEVEL_COM_LJWX_STARTER_SATOKEN_LOG=OFF`. Sa-Token's SLF4J adapter serializes the global configuration independently of the application-level `isLog` switch, so removing this logger policy reopens the startup disclosure path. The Deployment pod-template annotation deliberately changes when this policy changes so ConfigMap updates trigger a rolling restart.
Evidence may include only revision, image digest, readiness, smoke-test status, guard status, operator, and timestamps.
@@ -0,0 +1,53 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ljwx-health-cloudflared
spec:
podSelector:
matchLabels:
app.kubernetes.io/instance: ljwx-health-cloudflared
policyTypes: ["Ingress", "Egress"]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: observability
podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
ports:
- protocol: TCP
port: 2000
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: infra
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/component: controller
ports:
- protocol: TCP
port: 80
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- protocol: TCP
port: 7844
- protocol: UDP
port: 7844
+178
View File
@@ -0,0 +1,178 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-health-cloudflared-config
labels:
app.kubernetes.io/name: ljwx-health-cloudflared
app.kubernetes.io/instance: ljwx-health-cloudflared
app.kubernetes.io/component: edge
data:
config.yaml: |
tunnel: 70479f3e-caaf-4528-b713-413cc27e8ad0
credentials-file: /etc/cloudflared/credentials/credentials.json
metrics: 0.0.0.0:2000
loglevel: info
ingress:
- hostname: health-admin.omniverseai.net
service: http://ingress-nginx-controller.infra.svc.cluster.local:80
originRequest:
httpHostHeader: health-admin.omniverseai.net
connectTimeout: 10s
- hostname: health-dashboard.omniverseai.net
service: http://ingress-nginx-controller.infra.svc.cluster.local:80
originRequest:
httpHostHeader: health-dashboard.omniverseai.net
connectTimeout: 10s
- service: http_status:404
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-health-cloudflared
labels:
app.kubernetes.io/name: ljwx-health-cloudflared
app.kubernetes.io/instance: ljwx-health-cloudflared
app.kubernetes.io/component: edge
spec:
replicas: 2
strategy:
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app.kubernetes.io/instance: ljwx-health-cloudflared
template:
metadata:
labels:
app.kubernetes.io/name: ljwx-health-cloudflared
app.kubernetes.io/instance: ljwx-health-cloudflared
app.kubernetes.io/component: edge
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
seccompProfile:
type: RuntimeDefault
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: topology.kubernetes.io/zone
labelSelector:
matchLabels:
app.kubernetes.io/instance: ljwx-health-cloudflared
- weight: 50
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app.kubernetes.io/instance: ljwx-health-cloudflared
containers:
- name: cloudflared
image: cloudflare/cloudflared@sha256:6d91c121b803126f7a5344005d17a9324788fc09d305b6e2560ec6040a7ae283
imagePullPolicy: IfNotPresent
args:
- tunnel
- --no-autoupdate
- --config
- /etc/cloudflared/config/config.yaml
- run
ports:
- name: metrics
containerPort: 2000
startupProbe:
httpGet:
path: /ready
port: metrics
periodSeconds: 5
failureThreshold: 30
readinessProbe:
httpGet:
path: /ready
port: metrics
periodSeconds: 10
livenessProbe:
httpGet:
path: /ready
port: metrics
periodSeconds: 30
failureThreshold: 3
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tunnel-config
mountPath: /etc/cloudflared/config
readOnly: true
- name: tunnel-credentials
mountPath: /etc/cloudflared/credentials
readOnly: true
- name: tmp
mountPath: /tmp
volumes:
- name: tunnel-config
configMap:
name: ljwx-health-cloudflared-config
- name: tunnel-credentials
secret:
secretName: ljwx-health-cloudflared-credentials
items:
- key: credentials.json
path: credentials.json
- name: tmp
emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
name: ljwx-health-cloudflared
labels:
app.kubernetes.io/name: ljwx-health-cloudflared
app.kubernetes.io/instance: ljwx-health-cloudflared
app.kubernetes.io/component: edge
spec:
selector:
app.kubernetes.io/instance: ljwx-health-cloudflared
ports:
- name: metrics
port: 2000
targetPort: metrics
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ljwx-health-cloudflared
spec:
minAvailable: 1
selector:
matchLabels:
app.kubernetes.io/instance: ljwx-health-cloudflared
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ljwx-health-cloudflared
labels:
release: monitoring
app.kubernetes.io/instance: ljwx-health-cloudflared
spec:
selector:
matchLabels:
app.kubernetes.io/instance: ljwx-health-cloudflared
endpoints:
- port: metrics
path: /metrics
interval: 30s
+46 -16
View File
@@ -13,17 +13,28 @@ metadata:
data:
# Spring 配置
SPRING_PROFILES_ACTIVE: "k8s"
# Sa-Token does not provide Spring Security's Actuator authorization context;
# when_authorized therefore exposed dependency details. Hide them explicitly.
MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS: "never"
MANAGEMENT_ENDPOINT_HEALTH_SHOW_COMPONENTS: "never"
# 数据库配置(连接 infra namespace)
DB_HOST: "mysql-lb.infra.svc.cluster.local"
DB_PORT: "3306"
# 数据库配置(连接 infra namespace - PostgreSQL)
DB_HOST: "postgres-lb.infra.svc.cluster.local"
DB_PORT: "5432"
DB_NAME: "ljwx_health"
DB_USER: "root"
# Runtime traffic must cross PostgreSQL RLS. Schema hooks keep a separate
# administrator identity and never inherit this application role.
DB_USER: "ljwx_app"
DB_MIGRATION_USER: "postgres"
# Spring DataSource 配置
SPRING_DATASOURCE_URL: "jdbc:mysql://mysql-lb.infra.svc.cluster.local:3306/ljwx_health?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"
SPRING_DATASOURCE_USERNAME: "root"
SPRING_DATASOURCE_DRIVER_CLASS_NAME: "com.mysql.cj.jdbc.Driver"
# #383 IA-09 staged authorization versions. The PreSync hook creates and
# verifies all PostgreSQL facts before this flag rolls the backend forward.
AUTHORIZATION_VERSION_ENABLED: "true"
# Spring DataSource 配置(PostgreSQL)
SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres-lb.infra.svc.cluster.local:5432/ljwx_health?currentSchema=public&stringtype=unspecified"
SPRING_DATASOURCE_USERNAME: "ljwx_app"
SPRING_DATASOURCE_DRIVER_CLASS_NAME: "org.postgresql.Driver"
# Redis 配置(连接 infra namespace)
REDIS_HOST: "redis-lb.infra.svc.cluster.local"
@@ -37,6 +48,9 @@ data:
# 日志配置
LOG_LEVEL: "info"
# Sa-Token adapter serializes the global config (including jwtSecretKey)
# independently of SaTokenConfig.isLog. Suppress that logger package.
LOGGING_LEVEL_COM_LJWX_STARTER_SATOKEN_LOG: "OFF"
# OSS 配置
OSS_BUCKET_NAME: "ljwx-health"
@@ -51,14 +65,30 @@ data:
LJWX_LICENSE_ENABLED: "false"
LJWX_LICENSE_TRIAL_MODE: "false"
# OpenTelemetry: traces go through OTLP/HTTP. Metrics and logs already have
# dedicated Prometheus and Loki pipelines, so do not duplicate them via OTel.
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector-opentelemetry-collector.tracing.svc.cluster.local:4318"
OTEL_EXPORTER_OTLP_PROTOCOL: "http/protobuf"
OTEL_TRACES_EXPORTER: "otlp"
OTEL_METRICS_EXPORTER: "none"
OTEL_LOGS_EXPORTER: "none"
OTEL_RESOURCE_ATTRIBUTES: "deployment.environment.name=production,service.namespace=ljwx-health"
# 时区配置
TZ: "Asia/Shanghai"
# Flyway 数据库迁移配置
# ✅ 启用 Flyway - 对已有生产库使用统一基线 V3.0.0
SPRING_FLYWAY_ENABLED: "true"
SPRING_FLYWAY_BASELINE_ON_MIGRATE: "true" # 已有数据库直接标记到统一基线
SPRING_FLYWAY_BASELINE_VERSION: "3.0.0"
SPRING_FLYWAY_LOCATIONS: "classpath:db/baseline"
SPRING_FLYWAY_VALIDATE_ON_MIGRATE: "true"
SPRING_FLYWAY_CLEAN_DISABLED: "true" # 生产环境禁用 clean
# Flyway 数据库迁移配置(PostgreSQL)
# Runtime migrations are disabled for K3s; schema is managed by infra/DB
# rollout tasks so application startup is not coupled to Flyway history drift.
SPRING_FLYWAY_ENABLED: "false"
SPRING_FLYWAY_BASELINE_ON_MIGRATE: "false"
SPRING_FLYWAY_LOCATIONS: "classpath:db/migration-pg"
SPRING_FLYWAY_VALIDATE_ON_MIGRATE: "false"
SPRING_FLYWAY_CLEAN_DISABLED: "true"
# #349 governed OLAP shadow. Defaults are deliberately fail closed; the
# production canary overlay owns the one-tenant activation.
LJWX_HEALTH_OUTBOX_KAFKA_ENABLED: "false"
LJWX_HEALTH_OUTBOX_KAFKA_ALLOWED_CUSTOMER_IDS: ""
LJWX_HEALTH_OUTBOX_KAFKA_ALLOW_ALL_TENANTS: "false"
LJWX_HEALTH_OUTBOX_KAFKA_BOOTSTRAP_SERVERS: "kafka-cluster-kafka-bootstrap.shared-base.svc.cluster.local:9092"
+59 -16
View File
@@ -5,7 +5,7 @@
# Usage: ./create-secret.sh
# =============================================================================
set -e
set -euo pipefail
# 颜色定义
RED='\033[0;31m'
@@ -16,6 +16,8 @@ NC='\033[0m' # No Color
# 默认值
NAMESPACE="ljwx-health"
SECRET_NAME="ljwx-health-secret"
MINIO_SOURCE_NAMESPACE="${MINIO_SOURCE_NAMESPACE:-infra}"
MINIO_SOURCE_SECRET="${MINIO_SOURCE_SECRET:-minio-credentials}"
# 从环境变量或提示用户输入
read_secret() {
@@ -23,13 +25,13 @@ read_secret() {
local prompt=$2
local default_value=$3
if [ -n "${!var_name}" ]; then
if [ -n "${!var_name:-}" ]; then
echo "${!var_name}"
elif [ -n "$default_value" ]; then
read -p "$prompt [$default_value]: " value
read -r -p "$prompt [$default_value]: " value
echo "${value:-$default_value}"
else
read -p "$prompt: " value
read -r -p "$prompt: " value
echo "$value"
fi
}
@@ -37,8 +39,13 @@ read_secret() {
echo -e "${GREEN}=== LJWX Health Secret 创建工具 ===${NC}\n"
# 数据库配置
echo -e "${YELLOW}数据库配置(连接 infra namespace MySQL)${NC}"
DB_PASSWORD=$(read_secret "DB_PASSWORD" "数据库密码" "123456")
echo -e "${YELLOW}数据库配置(连接 infra namespace PostgreSQL)${NC}"
DB_PASSWORD=$(read_secret "DB_PASSWORD" "Schema 管理账号密码" "123456")
DB_APP_PASSWORD=$(read_secret "DB_APP_PASSWORD" "ljwx_app 运行时账号密码" "")
if [ -z "$DB_APP_PASSWORD" ]; then
echo -e "${RED}DB_APP_PASSWORD 不得为空;请先为 ljwx_app 配置独立密码${NC}" >&2
exit 1
fi
# Redis 配置
echo -e "\n${YELLOW}Redis 配置(连接 infra namespace Redis)${NC}"
@@ -46,25 +53,59 @@ REDIS_PASSWORD=$(read_secret "REDIS_PASSWORD" "Redis 密码" "123456")
# OSS 配置
echo -e "\n${YELLOW}OSS 配置(MinIO)${NC}"
OSS_ACCESS_KEY=$(read_secret "OSS_ACCESS_KEY" "OSS Access Key" "minioadmin")
OSS_SECRET_KEY=$(read_secret "OSS_SECRET_KEY" "OSS Secret Key" "minioadmin")
if [ -z "${OSS_ACCESS_KEY:-}" ] && [ -z "${OSS_SECRET_KEY:-}" ] \
&& kubectl -n "$MINIO_SOURCE_NAMESPACE" get secret "$MINIO_SOURCE_SECRET" >/dev/null 2>&1; then
OSS_ACCESS_KEY="$(kubectl -n "$MINIO_SOURCE_NAMESPACE" get secret "$MINIO_SOURCE_SECRET" \
-o jsonpath='{.data.root-user}' | base64 -d)"
OSS_SECRET_KEY="$(kubectl -n "$MINIO_SOURCE_NAMESPACE" get secret "$MINIO_SOURCE_SECRET" \
-o jsonpath='{.data.root-password}' | base64 -d)"
echo -e "${GREEN}已从 ${MINIO_SOURCE_NAMESPACE}/${MINIO_SOURCE_SECRET} 读取凭据(值不显示)${NC}"
else
OSS_ACCESS_KEY=$(read_secret "OSS_ACCESS_KEY" "OSS Access Key" "")
OSS_SECRET_KEY=$(read_secret "OSS_SECRET_KEY" "OSS Secret Key" "")
fi
if [ -z "$OSS_ACCESS_KEY" ] || [ -z "$OSS_SECRET_KEY" ]; then
echo -e "${RED}OSS_ACCESS_KEY 与 OSS_SECRET_KEY 不得为空${NC}" >&2
exit 1
fi
echo -e "\n${YELLOW}应用安全配置${NC}"
SECRET_KEY=$(read_secret "SECRET_KEY" "Flask SECRET_KEY(留空则自动生成)" "")
if [ -z "$SECRET_KEY" ]; then
SECRET_KEY="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
echo -e "${YELLOW}未提供 SECRET_KEY,已自动生成随机值${NC}"
fi
SA_TOKEN_JWT_SECRET=$(read_secret "SA_TOKEN_JWT_SECRET" "Sa-Token JWT Secret(留空则自动生成)" "")
if [ -z "$SA_TOKEN_JWT_SECRET" ]; then
SA_TOKEN_JWT_SECRET="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
echo -e "${YELLOW}未提供 SA_TOKEN_JWT_SECRET,已自动生成随机值${NC}"
fi
if [ "${#SA_TOKEN_JWT_SECRET}" -lt 32 ]; then
echo -e "${RED}SA_TOKEN_JWT_SECRET 至少需要 32 个字符${NC}" >&2
exit 1
fi
# 创建 namespace(如果不存在)
echo -e "\n${GREEN}创建 namespace: $NAMESPACE${NC}"
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
# 删除旧 Secret(如果存在)
echo -e "${GREEN}删除旧 Secret(如果存在)${NC}"
kubectl delete secret $SECRET_NAME -n $NAMESPACE --ignore-not-found=true
# 创建新 Secret
echo -e "${GREEN}创建 Secret: $SECRET_NAME${NC}"
# 声明式创建或更新 Secret,避免先删除造成不可用窗口。
echo -e "${GREEN}创建或更新 Secret: $SECRET_NAME${NC}"
kubectl create secret generic $SECRET_NAME \
-n $NAMESPACE \
--from-literal=DB_PASSWORD="$DB_PASSWORD" \
--from-literal=DB_APP_PASSWORD="$DB_APP_PASSWORD" \
--from-literal=REDIS_PASSWORD="$REDIS_PASSWORD" \
--from-literal=OSS_ACCESS_KEY="$OSS_ACCESS_KEY" \
--from-literal=OSS_SECRET_KEY="$OSS_SECRET_KEY"
--from-literal=OSS_SECRET_KEY="$OSS_SECRET_KEY" \
--from-literal=SECRET_KEY="$SECRET_KEY" \
--from-literal=SA_TOKEN_JWT_SECRET="$SA_TOKEN_JWT_SECRET" \
--dry-run=client -o yaml | kubectl apply -f -
# Remove the apply annotation because it contains base64-encoded Secret values.
kubectl -n "$NAMESPACE" annotate secret "$SECRET_NAME" \
kubectl.kubernetes.io/last-applied-configuration- >/dev/null 2>&1 || true
# 验证
echo -e "\n${GREEN}验证 Secret:${NC}"
@@ -73,8 +114,10 @@ kubectl get secret $SECRET_NAME -n $NAMESPACE
echo -e "\n${GREEN}✅ Secret 创建成功!${NC}"
echo -e "\n${YELLOW}提示:${NC}"
echo " 1. Secret 包含敏感信息,请妥善保管"
echo " 2. 数据库连接地址: mysql-infra.infra.svc.cluster.local:3306"
echo " 2. 数据库连接地址: postgres-lb.infra.svc.cluster.local:5432"
echo " 3. Redis 连接地址: redis-infra.infra.svc.cluster.local:6379"
echo " 4. Argo CD 应用将通过 GitOps 自动部署"
echo " 5. 访问地址: http://health-admin.omniverseai.net (HTTP)"
echo " 6. 大屏地址: http://health-dashboard.omniverseai.net (HTTP)"
echo " 7. SECRET_KEY 已写入 Secret,可直接供 bigscreen 生产模式使用"
echo " 8. SA_TOKEN_JWT_SECRET 已写入 Secret;轮换后所有既有登录态失效"
+19 -2
View File
@@ -13,7 +13,6 @@ metadata:
component: frontend
version: v1.0.0
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
@@ -22,6 +21,8 @@ spec:
selector:
matchLabels:
app: ljwx-admin
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/managed-by: kustomize
component: frontend
template:
metadata:
@@ -32,10 +33,26 @@ spec:
annotations:
kubectl.kubernetes.io/restartedAt: "20260103-072537"
spec:
# Harbor 镜像拉取凭证
imagePullSecrets:
- name: ghcr-pull
- name: regcred
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: topology.kubernetes.io/zone
labelSelector:
matchLabels:
app: ljwx-admin
- weight: 50
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: ljwx-admin
containers:
- name: ljwx-admin
image: harbor.omniverseai.net/ljwx-health/ljwx-admin:latest
+21 -40
View File
@@ -32,32 +32,10 @@ spec:
annotations:
kubectl.kubernetes.io/restartedAt: "20260103-072537"
spec:
# Harbor 镜像拉取凭证
imagePullSecrets:
- name: ghcr-pull
- name: regcred
# 初始化容器 - 等待数据库就绪
initContainers:
- name: wait-for-mysql
image: busybox:1.35
command:
- sh
- -c
- |
echo "Waiting for MySQL to be ready..."
until nc -z mysql-lb.infra.svc.cluster.local 3306; do
echo "MySQL not ready, sleeping for 2 seconds..."
sleep 2
done
echo "MySQL is ready!"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 100m
memory: 256Mi
# 主容器
containers:
- name: ljwx-bigscreen
@@ -75,20 +53,18 @@ spec:
# 环境变量 - 从 Secret(敏感信息)
env:
# 数据库配置
- name: MYSQL_HOST
value: "mysql-lb.infra.svc.cluster.local"
- name: MYSQL_PORT
value: "3306"
- name: MYSQL_DATABASE
value: "ljwx_health"
- name: MYSQL_USER
value: "root"
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
# Backend API
- name: BOOT_API_URL
value: "http://ljwx-boot:9998"
# Legacy 5225 bookmark cutover (#172). Keep the base fail-safe;
# environment overlays can progress legacy -> allowlist -> redirect.
- name: BIGSCREEN_CUTOVER_MODE
value: "legacy"
- name: BIGSCREEN_CUTOVER_ADMIN_BASE_URL
value: "https://health-dashboard.omniverseai.net"
- name: BIGSCREEN_CUTOVER_TENANTS
value: ""
# Redis 配置
- name: REDIS_HOST
@@ -102,6 +78,11 @@ spec:
key: REDIS_PASSWORD
# Flask 配置
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: SECRET_KEY
- name: FLASK_ENV
value: "production"
- name: FLASK_APP
@@ -119,7 +100,7 @@ spec:
# 健康检查
livenessProbe:
httpGet:
path: /health
path: /bigscreen/api/health
port: 5225
scheme: HTTP
initialDelaySeconds: 60
@@ -130,7 +111,7 @@ spec:
readinessProbe:
httpGet:
path: /health
path: /bigscreen/api/health
port: 5225
scheme: HTTP
initialDelaySeconds: 30
@@ -141,7 +122,7 @@ spec:
startupProbe:
httpGet:
path: /health
path: /bigscreen/api/health
port: 5225
scheme: HTTP
initialDelaySeconds: 15
+38 -12
View File
@@ -13,7 +13,6 @@ metadata:
component: backend
version: v1.0.0
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
@@ -22,6 +21,8 @@ spec:
selector:
matchLabels:
app: ljwx-boot
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/managed-by: kustomize
component: backend
template:
metadata:
@@ -33,26 +34,44 @@ spec:
prometheus.io/scrape: "true"
prometheus.io/port: "9998"
prometheus.io/path: "/actuator/prometheus"
ops.ljwx.com/auth-log-policy: "sa-token-log-off-v1"
kubectl.kubernetes.io/restartedAt: "20260103-080347"
ops.ljwx.io/config-revision: "rls-restricted-role-minio-v2"
spec:
# Harbor 镜像拉取凭证
imagePullSecrets:
- name: ghcr-pull
- name: regcred
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: topology.kubernetes.io/zone
labelSelector:
matchLabels:
app: ljwx-boot
- weight: 50
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: ljwx-boot
# 初始化容器 - 等待数据库就绪
initContainers:
- name: wait-for-mysql
- name: wait-for-db
image: busybox:1.35
command:
- sh
- -c
- |
echo "Waiting for MySQL to be ready..."
until nc -z mysql-lb.infra.svc.cluster.local 3306; do
echo "MySQL not ready, sleeping for 2 seconds..."
echo "Waiting for PostgreSQL to be ready..."
until nc -z postgres-lb.infra.svc.cluster.local 5432; do
echo "PostgreSQL not ready, sleeping for 2 seconds..."
sleep 2
done
echo "MySQL is ready!"
echo "PostgreSQL is ready!"
resources:
requests:
cpu: 50m
@@ -83,14 +102,14 @@ spec:
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
key: DB_APP_PASSWORD
# Spring 数据库密码(新格式)
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
key: DB_APP_PASSWORD
# Redis 密码(旧格式)
- name: REDIS_PASSWORD
@@ -118,6 +137,13 @@ spec:
name: ljwx-health-secret
key: OSS_SECRET_KEY
# Sa-Token JWT 签名密钥(仅从运行时 Secret 注入)
- name: SA_TOKEN_JWT_SECRET
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: SA_TOKEN_JWT_SECRET
volumeMounts:
- name: app-logs
mountPath: /app/logs
@@ -159,10 +185,10 @@ spec:
path: /actuator/health
port: 9998
scheme: HTTP
initialDelaySeconds: 30
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
timeoutSeconds: 10
failureThreshold: 60
successThreshold: 1
# 生命周期
@@ -0,0 +1,101 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: health-recon
namespace: ljwx-health
labels:
app: health-recon
component: health-shadow
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: health-recon
component: health-shadow
template:
metadata:
labels:
app: health-recon
component: health-shadow
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: /actuator/prometheus
spec:
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-pull
- name: regcred
containers:
- name: health-recon
image: harbor.omniverseai.net/ljwx-health/health-recon:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
env:
- name: CLICKHOUSE_HOST
value: clickhouse.infra.svc.cluster.local
- name: CLICKHOUSE_PORT
value: "8123"
- name: CLICKHOUSE_DB
value: ljwx_health
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: ljwx-health-clickhouse-secret
key: CLICKHOUSE_USER
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-clickhouse-secret
key: CLICKHOUSE_PASSWORD
- name: PG_JDBC_URL
value: jdbc:postgresql://postgres-lb.infra.svc.cluster.local:5432/ljwx_health?connectTimeout=5&socketTimeout=30
- name: PG_USER
value: ljwx_app
- name: PG_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_APP_PASSWORD
- name: RECON_CRON
value: "0 30 1 * * ?"
- name: RECON_RUN_ON_START
value: "true"
- name: RECON_ALLOWED_CUSTOMER_IDS
value: "1"
- name: RECON_MAX_DIFF
value: "0"
- name: TZ
value: Asia/Shanghai
resources:
requests:
cpu: 50m
memory: 256Mi
limits:
cpu: 300m
memory: 640Mi
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 5
failureThreshold: 24
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
terminationGracePeriodSeconds: 30
@@ -0,0 +1,93 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: health-writer
namespace: ljwx-health
labels:
app: health-writer
component: health-shadow
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: health-writer
component: health-shadow
template:
metadata:
labels:
app: health-writer
component: health-shadow
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: /actuator/prometheus
spec:
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-pull
- name: regcred
containers:
- name: health-writer
image: harbor.omniverseai.net/ljwx-health/health-writer:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 8080
env:
- name: KAFKA_BOOTSTRAP_SERVERS
value: kafka-cluster-kafka-bootstrap.shared-base.svc.cluster.local:9092
- name: KAFKA_CONCURRENCY
value: "1"
- name: CLICKHOUSE_HOST
value: clickhouse.infra.svc.cluster.local
- name: CLICKHOUSE_PORT
value: "8123"
- name: CLICKHOUSE_DB
value: ljwx_health
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: ljwx-health-clickhouse-secret
key: CLICKHOUSE_USER
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-clickhouse-secret
key: CLICKHOUSE_PASSWORD
- name: LJWX_CONSUMER_HEALTH_RAW_VENDOR_COLUMNS_ENABLED
value: "true"
- name: TZ
value: Asia/Shanghai
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 768Mi
startupProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 5
failureThreshold: 24
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: http
periodSeconds: 20
timeoutSeconds: 3
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: http
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
terminationGracePeriodSeconds: 30
@@ -0,0 +1,193 @@
{
"annotations": {"list": []},
"editable": true,
"graphTooltip": 1,
"panels": [
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"mappings": [], "thresholds": {"mode": "absolute", "steps": [{"color": "red"}, {"color": "green", "value": 1}]}, "unit": "short"}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 0, "y": 0},
"id": 1,
"options": {"colorMode": "background", "graphMode": "none", "justifyMode": "auto", "orientation": "auto", "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}, "textMode": "auto"},
"targets": [{"expr": "max(up{namespace=\"ljwx-health\",service=\"ljwx-boot\"})", "refId": "A"}],
"title": "Boot target up",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"mappings": [], "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 0.001}]}, "unit": "percentunit"}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 6, "y": 0},
"id": 2,
"options": {"colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}, "textMode": "auto"},
"targets": [{"expr": "ljwx_health:http_5xx:ratio5m", "refId": "A"}],
"title": "5xx ratio / 5m",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"mappings": [], "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 1}]}, "unit": "s"}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 12, "y": 0},
"id": 3,
"options": {"colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}, "textMode": "auto"},
"targets": [{"expr": "ljwx_health:http_latency_p95:5m", "refId": "A"}],
"title": "P95 latency / 5m",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"mappings": [], "thresholds": {"mode": "absolute", "steps": [{"color": "green"}]}, "unit": "reqps"}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 18, "y": 0},
"id": 4,
"options": {"colorMode": "value", "graphMode": "area", "justifyMode": "auto", "orientation": "auto", "reduceOptions": {"calcs": ["lastNotNull"], "fields": "", "values": false}, "textMode": "auto"},
"targets": [{"expr": "ljwx_health:http_requests:rate5m", "refId": "A"}],
"title": "Request rate / 5m",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"color": {"mode": "palette-classic"}, "custom": {"axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "drawStyle": "line", "fillOpacity": 15, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "showPoints": "never", "spanNulls": false, "stacking": {"group": "A", "mode": "none"}}, "mappings": [], "thresholds": {"mode": "absolute", "steps": [{"color": "green"}]}, "unit": "percentunit"}, "overrides": []},
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 5},
"id": 5,
"options": {"legend": {"calcs": ["lastNotNull", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true}, "tooltip": {"mode": "single", "sort": "none"}},
"targets": [{"expr": "sum(jvm_memory_used_bytes{namespace=\"ljwx-health\",service=\"ljwx-boot\",area=\"heap\"}) / clamp_min(sum(jvm_memory_max_bytes{namespace=\"ljwx-health\",service=\"ljwx-boot\",area=\"heap\"}), 1)", "legendFormat": "heap", "refId": "A"}],
"title": "JVM heap saturation",
"type": "timeseries"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"color": {"mode": "palette-classic"}, "custom": {"axisCenteredZero": false, "axisColorMode": "text", "axisLabel": "", "axisPlacement": "auto", "drawStyle": "line", "fillOpacity": 15, "lineInterpolation": "linear", "lineWidth": 1, "pointSize": 5, "showPoints": "never", "spanNulls": false, "stacking": {"group": "A", "mode": "none"}}, "mappings": [], "thresholds": {"mode": "absolute", "steps": [{"color": "green"}]}, "unit": "percentunit"}, "overrides": []},
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 5},
"id": 6,
"options": {"legend": {"calcs": ["lastNotNull", "max"], "displayMode": "table", "placement": "bottom", "showLegend": true}, "tooltip": {"mode": "single", "sort": "none"}},
"targets": [{"expr": "sum(hikaricp_connections_active{namespace=\"ljwx-health\",service=\"ljwx-boot\"}) / clamp_min(sum(hikaricp_connections_max{namespace=\"ljwx-health\",service=\"ljwx-boot\"}), 1)", "legendFormat": "pool", "refId": "A"}],
"title": "Database pool saturation",
"type": "timeseries"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "reqps", "thresholds": {"mode": "absolute", "steps": [{"color": "green"}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 0, "y": 13},
"id": 7,
"targets": [{"expr": "ljwx_health:bigscreen_bff_qps:5m{tenant=~\"$tenant\"}", "legendFormat": "tenant {{tenant}}", "refId": "A"}],
"title": "Bigscreen BFF QPS",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "percentunit", "min": 0, "max": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "red"}, {"color": "green", "value": 0.995}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 6, "y": 13},
"id": 8,
"targets": [{"expr": "ljwx_health:bigscreen_bff_success:ratio5m{tenant=~\"$tenant\"}", "legendFormat": "tenant {{tenant}}", "refId": "A"}],
"title": "BFF success ratio (target 99.5%)",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "s", "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 0.5}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 12, "y": 13},
"id": 9,
"targets": [
{"expr": "ljwx_health:bigscreen_bff_latency_p95:5m{tenant=~\"$tenant\"}", "legendFormat": "P95 {{tenant}}", "refId": "A"},
{"expr": "ljwx_health:bigscreen_bff_latency_p99:5m{tenant=~\"$tenant\"}", "legendFormat": "P99 {{tenant}}", "refId": "B"}
],
"title": "BFF P95 / P99 latency",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "percentunit", "min": 0, "max": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "red"}, {"color": "green", "value": 0.7}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 18, "y": 13},
"id": 10,
"targets": [{"expr": "ljwx_health:bigscreen_cache_hit:ratio5m{tenant=~\"$tenant\"}", "legendFormat": "tenant {{tenant}}", "refId": "A"}],
"title": "Redis cache hit ratio (target 70%)",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "percentunit", "min": 0, "max": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "red"}, {"color": "green", "value": 0.3}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 0, "y": 18},
"id": 11,
"targets": [{"expr": "ljwx_health:bigscreen_snapshot_304:ratio5m{tenant=~\"$tenant\"}", "legendFormat": "tenant {{tenant}}", "refId": "A"}],
"title": "Snapshot 304 ratio (target 30%)",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "s", "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 0.5}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 6, "y": 18},
"id": 12,
"targets": [{"expr": "ljwx_health:bigscreen_snapshot_build_p95:5m{tenant=~\"$tenant\"}", "legendFormat": "tenant {{tenant}}", "refId": "A"}],
"title": "Snapshot DB aggregation P95",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "percentunit", "min": 0, "max": 1, "thresholds": {"mode": "absolute", "steps": [{"color": "red"}, {"color": "green", "value": 0.995}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 12, "y": 18},
"id": 13,
"targets": [{"expr": "ljwx_health:bigscreen_kiosk_refresh_success:ratio5m{tenant=~\"$tenant\"}", "legendFormat": "tenant {{tenant}}", "refId": "A"}],
"title": "Kiosk refresh success ratio",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "s", "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 2}]}}, "overrides": []},
"gridPos": {"h": 5, "w": 6, "x": 18, "y": 18},
"id": 14,
"targets": [{"expr": "ljwx_health:bigscreen_kiosk_update_delay_p95:5m{tenant=~\"$tenant\"}", "legendFormat": "tenant {{tenant}}", "refId": "A"}],
"title": "generatedAt to kiosk update P95",
"type": "stat"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "s", "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 300}]}}, "overrides": []},
"gridPos": {"h": 7, "w": 8, "x": 0, "y": 23},
"id": 15,
"targets": [
{"expr": "ljwx_health:bigscreen_kiosk_heartbeat_age:seconds{tenant=~\"$tenant\"}", "legendFormat": "heartbeat age {{tenant}}", "refId": "A"},
{"expr": "ljwx_health:bigscreen_kiosk_recovery_p95:1h{tenant=~\"$tenant\"}", "legendFormat": "recovery P95 {{tenant}}", "refId": "B"}
],
"title": "Kiosk heartbeat and recovery",
"type": "timeseries"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "short", "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 1}]}}, "overrides": []},
"gridPos": {"h": 7, "w": 8, "x": 8, "y": 23},
"id": 16,
"targets": [
{"expr": "sum by (tenant, type) (increase(bigscreen_kiosk_js_errors_total{tenant=~\"$tenant\"}[$__range]))", "legendFormat": "{{type}} {{tenant}}", "refId": "A"},
{"expr": "sum by (tenant, event) (increase(bigscreen_kiosk_connectivity_total{tenant=~\"$tenant\"}[$__range]))", "legendFormat": "{{event}} {{tenant}}", "refId": "B"}
],
"title": "JS errors and offline/recovery events",
"type": "timeseries"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "bytes", "thresholds": {"mode": "absolute", "steps": [{"color": "green"}, {"color": "red", "value": 157286400}]}}, "overrides": []},
"gridPos": {"h": 7, "w": 8, "x": 16, "y": 23},
"id": 17,
"targets": [{"expr": "max by (tenant) (max_over_time(bigscreen_kiosk_js_heap_bytes{tenant=~\"$tenant\"}[24h])) - min by (tenant) (min_over_time(bigscreen_kiosk_js_heap_bytes{tenant=~\"$tenant\"}[24h]))", "legendFormat": "24h growth {{tenant}}", "refId": "A"}],
"title": "Kiosk 24h JS heap growth",
"type": "timeseries"
},
{
"datasource": {"type": "prometheus", "uid": "prometheus"},
"fieldConfig": {"defaults": {"unit": "short"}, "overrides": []},
"gridPos": {"h": 7, "w": 24, "x": 0, "y": 30},
"id": 18,
"targets": [{"expr": "ALERTS{alertname=~\"LjwxHealthBigscreen.*\",tenant=~\"$tenant\",alertstate=\"firing\"}", "format": "table", "instant": true, "refId": "A"}],
"title": "Active bigscreen alerts",
"type": "table"
}
],
"refresh": "30s",
"schemaVersion": 39,
"tags": ["ljwx-health", "slo", "operations", "bigscreen", "trial"],
"templating": {"list": [{"name": "tenant", "label": "Tenant", "type": "query", "datasource": {"type": "prometheus", "uid": "prometheus"}, "query": {"query": "label_values(bigscreen_bff_requests_total, tenant)", "refId": "StandardVariableQuery"}, "definition": "label_values(bigscreen_bff_requests_total, tenant)", "includeAll": true, "allValue": ".*", "multi": true, "refresh": 1, "current": {"text": "All", "value": "$__all", "selected": true}}]},
"time": {"from": "now-24h", "to": "now"},
"timezone": "browser",
"title": "LJWX Health / SLO and Bigscreen Trial",
"uid": "ljwx-health-slo",
"version": 2
}
+65
View File
@@ -0,0 +1,65 @@
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ljwx-boot
namespace: ljwx-health
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ljwx-boot
minReplicas: 2
maxReplicas: 4
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 1
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
---
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: ljwx-admin
namespace: ljwx-health
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: ljwx-admin
minReplicas: 2
maxReplicas: 4
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 1
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 60
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
+64
View File
@@ -0,0 +1,64 @@
---
# Public clients use /api as the gateway prefix. Boot controllers are rooted at
# /auth, /sys_*, /api/v2, etc.; strip exactly the outer gateway prefix.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-api-ingress
namespace: ljwx-health
labels:
app: ljwx-health
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, PATCH, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
spec:
ingressClassName: nginx
rules:
- host: health-admin.lingjingwanxiang.cn
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: ljwx-boot
port:
number: 9998
- host: health-dashboard.lingjingwanxiang.cn
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: ljwx-boot
port:
number: 9998
- host: health-admin.omniverseai.net
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: ljwx-boot
port:
number: 9998
- host: health-dashboard.omniverseai.net
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: ljwx-boot
port:
number: 9998
+13 -24
View File
@@ -3,8 +3,8 @@
# ljwx-health Ingress Configuration
# 路由配置:
# - health-admin.lingjingwanxiang.cn -> ljwx-admin (Vue3 前端)
# - health-dashboard.lingjingwanxiang.cn -> ljwx-bigscreen (Flask 大屏)
# - health-admin.lingjingwanxiang.cn/api -> ljwx-boot (后端 API)
# - health-dashboard.lingjingwanxiang.cn -> ljwx-admin (Vue3 大屏)
# - API 路由由 ingress-api.yaml 独立处理并去掉外层 /api 前缀
# - legacy omniverseai.net hosts remain during cutover
# =============================================================================
apiVersion: networking.k8s.io/v1
@@ -15,9 +15,13 @@ metadata:
labels:
app: ljwx-health
annotations:
# Public TLS is terminated by Cloudflare for the active omniverseai.net
# hosts. Keep the origin on HTTP until the lingjingwanxiang.cn cutover DNS
# records exist; enabling cert-manager/ssl-redirect before that point makes
# ACME issuance fail and breaks the current tunnel origin path.
# CORS 配置
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/cors-allow-origin: "https://health-admin.lingjingwanxiang.cn, https://health-dashboard.lingjingwanxiang.cn, https://health-admin.omniverseai.net, https://health-dashboard.omniverseai.net"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
# 请求大小限制
@@ -27,7 +31,7 @@ metadata:
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
spec:
ingressClassName: traefik
ingressClassName: nginx
rules:
# =========================================================================
# Rule 1: health-admin.lingjingwanxiang.cn
@@ -35,14 +39,6 @@ spec:
- host: health-admin.lingjingwanxiang.cn
http:
paths:
# 后端 API 路由
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
# 前端静态资源
- path: /
pathType: Prefix
@@ -58,24 +54,17 @@ spec:
- host: health-dashboard.lingjingwanxiang.cn
http:
paths:
# 大屏应用
# Vue3 大屏应用
- path: /
pathType: Prefix
backend:
service:
name: ljwx-bigscreen
name: ljwx-admin
port:
number: 5001
number: 80
- host: health-admin.omniverseai.net
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
- path: /
pathType: Prefix
backend:
@@ -90,6 +79,6 @@ spec:
pathType: Prefix
backend:
service:
name: ljwx-bigscreen
name: ljwx-admin
port:
number: 5001
number: 80
@@ -1,55 +0,0 @@
---
# =============================================================================
# Flyway Repair Job
# 修复失败的迁移记录,清理 flyway_schema_history 表
# =============================================================================
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-health-flyway-repair
namespace: ljwx-health
labels:
app: ljwx-health
component: flyway-repair
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "1" # 在 init-db (wave 0) 之后运行
spec:
ttlSecondsAfterFinished: 300
backoffLimit: 3
template:
metadata:
labels:
app: ljwx-health
component: flyway-repair
spec:
restartPolicy: Never
containers:
- name: flyway-repair
image: mysql:8.0
command:
- sh
- -c
- |
set -eu
echo "检查并修复 Flyway schema history..."
mysql -h mysql-lb.infra.svc.cluster.local -u root -p${MYSQL_ROOT_PASSWORD} ljwx_health <<EOF
DROP TABLE IF EXISTS flyway_schema_history;
SELECT 'Flyway schema history table dropped. Flyway will recreate it on next run.' AS result;
EOF
echo "✅ Flyway schema history 修复完成"
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
@@ -0,0 +1,56 @@
apiVersion: batch/v1
kind: Job
metadata:
name: health-shadow-clickhouse-init
namespace: ljwx-health
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
argocd.argoproj.io/sync-wave: "-1"
spec:
backoffLimit: 3
activeDeadlineSeconds: 300
template:
spec:
restartPolicy: OnFailure
automountServiceAccountToken: false
containers:
- name: clickhouse-schema-init
image: clickhouse/clickhouse-server:24.3.12.75
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -ec
- |
clickhouse-client \
--host clickhouse.infra.svc.cluster.local \
--port 9000 \
--user "$CLICKHOUSE_USER" \
--password "$CLICKHOUSE_PASSWORD" \
--multiquery < /schema/clickhouse-health-raw.sql
env:
- name: CLICKHOUSE_USER
valueFrom:
secretKeyRef:
name: ljwx-health-clickhouse-secret
key: CLICKHOUSE_USER
- name: CLICKHOUSE_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-clickhouse-secret
key: CLICKHOUSE_PASSWORD
volumeMounts:
- name: schema
mountPath: /schema
readOnly: true
resources:
requests:
cpu: 25m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
volumes:
- name: schema
configMap:
name: ljwx-health-shadow-clickhouse-schema
@@ -0,0 +1,73 @@
apiVersion: batch/v1
kind: Job
metadata:
name: health-shadow-kafka-init
namespace: ljwx-health
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation,HookSucceeded
argocd.argoproj.io/sync-wave: "-1"
spec:
backoffLimit: 3
activeDeadlineSeconds: 300
template:
spec:
restartPolicy: OnFailure
automountServiceAccountToken: false
containers:
- name: kafka-topic-init
image: apache/kafka:4.1.0
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -ec
- |
BOOTSTRAP=kafka-cluster-kafka-bootstrap.shared-base.svc.cluster.local:9092
/opt/kafka/bin/kafka-topics.sh --bootstrap-server "$BOOTSTRAP" \
--create --if-not-exists --topic health_raw \
--partitions 3 --replication-factor 1 \
--config retention.ms=604800000
/opt/kafka/bin/kafka-topics.sh --bootstrap-server "$BOOTSTRAP" \
--create --if-not-exists --topic health_raw.DLT \
--partitions 1 --replication-factor 1 \
--config retention.ms=1209600000
current_partitions="$(/opt/kafka/bin/kafka-topics.sh --bootstrap-server "$BOOTSTRAP" \
--describe --topic health_raw \
| awk 'NR == 1 { for (i = 1; i <= NF; i++) if ($i == "PartitionCount:") { print $(i + 1); exit } }')"
case "$current_partitions" in
''|*[!0-9]*)
echo "unable to determine health_raw partition count: $current_partitions" >&2
exit 1
;;
esac
if [ "$current_partitions" -lt 3 ]; then
/opt/kafka/bin/kafka-topics.sh --bootstrap-server "$BOOTSTRAP" \
--alter --topic health_raw --partitions 3
fi
/opt/kafka/bin/kafka-configs.sh --bootstrap-server "$BOOTSTRAP" \
--alter --entity-type topics --entity-name health_raw \
--add-config retention.ms=604800000
/opt/kafka/bin/kafka-configs.sh --bootstrap-server "$BOOTSTRAP" \
--alter --entity-type topics --entity-name health_raw.DLT \
--add-config retention.ms=1209600000
main_desc="$(/opt/kafka/bin/kafka-topics.sh --bootstrap-server "$BOOTSTRAP" \
--describe --topic health_raw)"
dlt_desc="$(/opt/kafka/bin/kafka-topics.sh --bootstrap-server "$BOOTSTRAP" \
--describe --topic health_raw.DLT)"
main_config="$(/opt/kafka/bin/kafka-configs.sh --bootstrap-server "$BOOTSTRAP" \
--describe --entity-type topics --entity-name health_raw)"
dlt_config="$(/opt/kafka/bin/kafka-configs.sh --bootstrap-server "$BOOTSTRAP" \
--describe --entity-type topics --entity-name health_raw.DLT)"
printf '%s\n' "$main_desc"
printf '%s\n' "$dlt_desc"
printf '%s\n' "$main_desc" | grep -q 'PartitionCount: 3'
printf '%s\n' "$dlt_desc" | grep -q 'PartitionCount: 1'
printf '%s\n' "$main_config" | grep -q 'retention.ms=604800000'
printf '%s\n' "$dlt_config" | grep -q 'retention.ms=1209600000'
resources:
requests:
cpu: 25m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
-59
View File
@@ -1,59 +0,0 @@
---
# =============================================================================
# Database Initialization Job
# 在 ljwx-boot 启动前创建数据库,让 Flyway 可以运行迁移
# =============================================================================
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-health-init-db
namespace: ljwx-health
labels:
app: ljwx-health
component: init-db
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "0" # 首先运行
spec:
ttlSecondsAfterFinished: 300 # 5分钟后自动清理
backoffLimit: 3
template:
metadata:
labels:
app: ljwx-health
component: init-db
spec:
restartPolicy: Never
containers:
- name: init-database
image: mysql:8.0
command:
- sh
- -c
- |
echo "检查并创建 ljwx_health 数据库..."
mysql -h mysql-lb.infra.svc.cluster.local -u root -p${MYSQL_ROOT_PASSWORD} <<EOF
CREATE DATABASE IF NOT EXISTS ljwx_health
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
SHOW DATABASES LIKE 'ljwx_health';
SELECT 'Database ljwx_health is ready' AS status;
EOF
echo "✅ 数据库初始化完成"
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
+41 -5
View File
@@ -7,17 +7,53 @@ namespace: ljwx-health
resources:
- namespace.yaml
- configmap.yaml
- job-init-db.yaml
- job-flyway-repair.yaml
- deployment-boot.yaml
- deployment-admin.yaml
- deployment-bigscreen.yaml
- deployment-health-writer.yaml
- deployment-health-recon.yaml
- service-boot.yaml
- service-boot-compat.yaml
- service-admin.yaml
- service-bigscreen.yaml
- service-health-shadow.yaml
- job-health-shadow-kafka-init.yaml
- job-health-shadow-clickhouse-init.yaml
- ingress.yaml
- ingress-api.yaml
- servicemonitor.yaml
- prometheusrule.yaml
- prometheusrule-health-shadow.yaml
- servicemonitor-health-shadow.yaml
- pdb.yaml
- hpa.yaml
- networkpolicy.yaml
- cloudflared.yaml
- cloudflared-networkpolicy.yaml
commonLabels:
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/managed-by: kustomize
generatorOptions:
disableNameSuffixHash: true
configMapGenerator:
- name: ljwx-health-shadow-clickhouse-schema
files:
- clickhouse-health-raw.sql=sql/health-shadow/clickhouse-health-raw.sql
options:
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "-2"
- name: grafana-dashboard-ljwx-health-slo
files:
- ljwx-health-slo-dashboard.json=grafana/ljwx-health-slo-dashboard.json
options:
labels:
grafana_dashboard: "1"
app.kubernetes.io/component: observability
labels:
- includeSelectors: false
includeTemplates: true
pairs:
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/managed-by: kustomize
+33
View File
@@ -0,0 +1,33 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ljwx-health-ingress
namespace: ljwx-health
spec:
podSelector:
matchExpressions:
- key: app
operator: In
values:
- ljwx-boot
- ljwx-admin
- health-writer
- health-recon
policyTypes:
- Ingress
ingress:
- from:
- podSelector: {}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: infra
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/component: controller
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: observability
podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
+23
View File
@@ -0,0 +1,23 @@
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ljwx-boot
namespace: ljwx-health
spec:
minAvailable: 1
selector:
matchLabels:
app: ljwx-boot
component: backend
---
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ljwx-admin
namespace: ljwx-health
spec:
minAvailable: 1
selector:
matchLabels:
app: ljwx-admin
component: frontend
@@ -0,0 +1,66 @@
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: ljwx-health-shadow
namespace: ljwx-health
labels:
release: monitoring
spec:
groups:
- name: ljwx_health_shadow
interval: 30s
rules:
- alert: LjwxHealthShadowWriterUnavailable
expr: absent(up{namespace="ljwx-health",service="health-writer"}) or up{namespace="ljwx-health",service="health-writer"} == 0
for: 5m
labels:
severity: warning
category: data-integrity
annotations:
summary: Health shadow writer is unavailable
description: PostgreSQL remains authoritative; keep publisher canary fail closed and investigate Kafka/ClickHouse.
- alert: LjwxHealthShadowReconUnavailable
expr: absent(up{namespace="ljwx-health",service="health-recon"}) or up{namespace="ljwx-health",service="health-recon"} == 0
for: 5m
labels:
severity: warning
category: data-integrity
annotations:
summary: Health shadow reconciliation is unavailable
description: No promotion decision is allowed while the recon runner is unavailable.
- alert: LjwxHealthShadowWriterFailures
expr: increase(health_shadow_writer_batches_total{result="failed"}[10m]) > 0
for: 2m
labels:
severity: warning
category: data-integrity
annotations:
summary: Health shadow writer batch failed
description: The Kafka batch remains unacknowledged; PostgreSQL upload is unaffected.
- alert: LjwxHealthShadowReconMismatch
expr: health_olap_recon_missing_count{customer_id="0"} > 0 or health_olap_recon_duplicate_count{customer_id="0"} > 0
for: 5m
labels:
severity: critical
category: data-integrity
annotations:
summary: Health shadow reconciliation mismatch
description: Missing or duplicate business rows fail the seven-day promotion gate.
- alert: LjwxHealthShadowReconLagHigh
expr: health_olap_recon_lag_seconds{customer_id="0"} > 3600
for: 10m
labels:
severity: warning
category: data-integrity
annotations:
summary: Health shadow lag exceeds one hour
description: Keep ClickHouse out of customer-visible reads until lag returns within threshold.
- alert: LjwxHealthShadowDisallowedTenant
expr: health_olap_recon_disallowed_tenant_count{customer_id="0"} > 0
for: 1m
labels:
severity: critical
category: data-integrity
annotations:
summary: Health shadow contains a non-canary tenant
description: Disable the publisher canary and investigate tenant-gate enforcement before any promotion.
+671
View File
@@ -0,0 +1,671 @@
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: ljwx-health-slo
namespace: ljwx-health
labels:
release: monitoring
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: observability
spec:
groups:
- name: ljwx_health_availability
interval: 30s
rules:
- alert: LjwxHealthDeploymentUnavailable
expr: kube_deployment_status_replicas_available{namespace="ljwx-health",deployment=~"ljwx-(boot|admin)"} < 1
for: 2m
labels:
severity: critical
category: availability
annotations:
summary: "LJWX Health deployment is unavailable"
description: "{{ $labels.deployment }} has had no available replica for more than two minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthBootMetricsTargetMissing
expr: absent(up{namespace="ljwx-health",service="ljwx-boot"})
for: 5m
labels:
severity: critical
category: observability
service: ljwx-boot
annotations:
summary: "Prometheus has no ljwx-boot target"
description: "The application may be healthy, but its metrics target has been absent for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthBootMetricsTargetDown
expr: up{namespace="ljwx-health",service="ljwx-boot"} == 0
for: 2m
labels:
severity: critical
category: availability
service: ljwx-boot
annotations:
summary: "Prometheus cannot scrape ljwx-boot"
description: "The ljwx-boot metrics target has been down for more than two minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthPodRestarting
expr: increase(kube_pod_container_status_restarts_total{namespace="ljwx-health",container=~"ljwx-(boot|admin)"}[30m]) > 2
for: 5m
labels:
severity: warning
category: availability
annotations:
summary: "LJWX Health pod is restarting repeatedly"
description: "{{ $labels.pod }} restarted more than twice in 30 minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- name: ljwx_health_golden_signals
interval: 30s
rules:
- record: ljwx_health:http_requests:rate5m
expr: sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",uri!~"/actuator.*"}[5m]))
- record: ljwx_health:http_5xx:ratio5m
expr: |
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",status=~"5..",uri!~"/actuator.*"}[5m]))
/
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",uri!~"/actuator.*"}[5m])), 0.001)
- record: ljwx_health:http_5xx:ratio1h
expr: |
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",status=~"5..",uri!~"/actuator.*"}[1h]))
/
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",uri!~"/actuator.*"}[1h])), 0.001)
- record: ljwx_health:http_5xx:ratio30m
expr: |
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",status=~"5..",uri!~"/actuator.*"}[30m]))
/
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",uri!~"/actuator.*"}[30m])), 0.001)
- record: ljwx_health:http_5xx:ratio6h
expr: |
sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",status=~"5..",uri!~"/actuator.*"}[6h]))
/
clamp_min(sum(rate(http_server_requests_seconds_count{namespace="ljwx-health",service="ljwx-boot",uri!~"/actuator.*"}[6h])), 0.001)
- record: ljwx_health:http_latency_p95:5m
expr: |
histogram_quantile(
0.95,
sum(rate(http_server_requests_seconds_bucket{namespace="ljwx-health",service="ljwx-boot",uri!~"/actuator.*"}[5m])) by (le)
)
- alert: LjwxHealthBootHighErrorRate
expr: ljwx_health:http_5xx:ratio5m > 0.05
for: 5m
labels:
severity: warning
category: errors
service: ljwx-boot
annotations:
summary: "ljwx-boot 5xx ratio is above 5%"
description: "The five-minute server error ratio has remained above 5%."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthBootHighP95Latency
expr: ljwx_health:http_latency_p95:5m > 1
for: 10m
labels:
severity: warning
category: latency
service: ljwx-boot
annotations:
summary: "ljwx-boot P95 latency is above one second"
description: "The five-minute P95 latency has remained above one second."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthBootJvmHeapHigh
expr: sum(jvm_memory_used_bytes{namespace="ljwx-health",service="ljwx-boot",area="heap"}) / clamp_min(sum(jvm_memory_max_bytes{namespace="ljwx-health",service="ljwx-boot",area="heap"}), 1) > 0.85
for: 10m
labels:
severity: warning
category: saturation
service: ljwx-boot
annotations:
summary: "ljwx-boot JVM heap is above 85%"
description: "Heap saturation has remained above 85% for ten minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthBootDbPoolSaturated
expr: sum(hikaricp_connections_active{namespace="ljwx-health",service="ljwx-boot"}) / clamp_min(sum(hikaricp_connections_max{namespace="ljwx-health",service="ljwx-boot"}), 1) > 0.9
for: 5m
labels:
severity: critical
category: saturation
service: ljwx-boot
annotations:
summary: "ljwx-boot database pool is above 90%"
description: "The database connection pool has remained saturated for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- name: ljwx_health_slo_burn_rate
interval: 30s
rules:
- alert: LjwxHealthSloFastBurn
expr: ljwx_health:http_5xx:ratio5m > 0.0144 and ljwx_health:http_5xx:ratio1h > 0.0144
for: 2m
labels:
severity: critical
category: slo
service: ljwx-boot
slo_target: "99.9"
annotations:
summary: "LJWX Health is consuming error budget rapidly"
description: "The 5m and 1h windows exceed the 14.4x burn threshold for the 99.9% SLO."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthSloSlowBurn
expr: ljwx_health:http_5xx:ratio30m > 0.006 and ljwx_health:http_5xx:ratio6h > 0.006
for: 15m
labels:
severity: warning
category: slo
service: ljwx-boot
slo_target: "99.9"
annotations:
summary: "LJWX Health is steadily consuming error budget"
description: "The 30m and 6h windows exceed the 6x burn threshold for the 99.9% SLO."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- name: ljwx_health_realtime_score_canary
interval: 30s
rules:
- record: ljwx_health:realtime_score_freshness_p95:5m
expr: |
histogram_quantile(
0.95,
sum(rate(health_realtime_score_freshness_seconds_bucket{namespace="ljwx-health",service="ljwx-boot"}[5m])) by (le)
)
- record: ljwx_health:realtime_score_write_amplification:5m
expr: |
sum(rate(health_realtime_score_flush_total{namespace="ljwx-health",service="ljwx-boot",result="success"}[5m]))
/
clamp_min(sum(rate(health_realtime_score_events_total{namespace="ljwx-health",service="ljwx-boot",result="accepted"}[5m])), 0.001)
- alert: LjwxHealthRealtimeScoreFreshnessSloBreached
expr: |
ljwx_health:realtime_score_freshness_p95:5m > 5
and
sum(rate(health_realtime_score_events_total{namespace="ljwx-health",service="ljwx-boot",result="accepted"}[5m])) > 0
for: 10m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score freshness P95 is above five seconds"
description: "Persisted events have taken more than five seconds to become visible in Redis for ten minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- alert: LjwxHealthRealtimeScoreListenerFailures
expr: |
sum(rate(health_realtime_score_events_total{namespace="ljwx-health",service="ljwx-boot",result="failed"}[5m])) > 0
for: 5m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score listener is failing"
description: "Persisted health events are failing in the realtime-score listener window."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- alert: LjwxHealthRealtimeScoreExecutorSaturated
expr: |
sum(rate(health_realtime_score_events_total{namespace="ljwx-health",service="ljwx-boot",result="rejected"}[5m])) > 0
for: 2m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score executor is rejecting work"
description: "The isolated realtime-score executor is saturated; upload ACK remains isolated and overnight ground truth is the recovery path."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- alert: LjwxHealthRealtimeScoreExecutorBacklog
expr: |
executor_queued_tasks{namespace="ljwx-health",service="ljwx-boot",name="healthRealtimeScoreTaskExecutor"} > 80
for: 5m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score executor queue is near capacity"
description: "The isolated executor has held more than 80 queued event batches for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- alert: LjwxHealthRealtimeScoreFlushFailures
expr: |
sum(rate(health_realtime_score_flush_total{namespace="ljwx-health",service="ljwx-boot",result=~"failed|ack_failed"}[5m])) > 0
for: 5m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score merge writes are failing"
description: "A realtime-score database flush or post-commit acknowledgement has failed during the last five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- alert: LjwxHealthRealtimeScoreWriteAmplificationHigh
expr: |
ljwx_health:realtime_score_write_amplification:5m >= 0.2
and
sum(rate(health_realtime_score_events_total{namespace="ljwx-health",service="ljwx-boot",result="accepted"}[5m])) > 0
for: 15m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score write amplification is too high"
description: "Merged PostgreSQL writes have remained at or above 20 percent of accepted events."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- alert: LjwxHealthRealtimeScoreCalibrationIncomplete
expr: |
health_realtime_score_reconciliation_states{namespace="ljwx-health",service="ljwx-boot"} > 0
and
health_realtime_score_reconciliation_coverage_ratio{namespace="ljwx-health",service="ljwx-boot"} < 1
for: 15m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score calibration coverage is incomplete"
description: "Tenant {{ $labels.customer }} has realtime states without matching overnight ground truth."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- alert: LjwxHealthRealtimeScoreCalibrationDeviation
expr: |
health_realtime_score_reconciliation_exceeds{namespace="ljwx-health",service="ljwx-boot"} > 0
for: 5m
labels:
severity: warning
category: health-score
service: ljwx-boot
annotations:
summary: "Realtime health-score calibration deviation exceeded the threshold"
description: "Tenant {{ $labels.customer }} has users whose realtime score differs materially from overnight ground truth."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/02-architecture/business-data-chains/epic-360-operations.md"
- name: ljwx_health_edge_connectivity
interval: 30s
rules:
- alert: LjwxHealthCloudflaredUnavailable
expr: kube_deployment_status_replicas_available{namespace="ljwx-health",deployment="ljwx-health-cloudflared"} < 1
for: 2m
labels:
severity: critical
category: edge
annotations:
summary: "LJWX Health Cloudflare Tunnel is unavailable"
description: "No in-cluster cloudflared connector has been available for two minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/edge-tunnel.md"
- alert: LjwxHealthCloudflaredRedundancyLost
expr: sum(cloudflared_tunnel_ha_connections{namespace="ljwx-health"}) < 2
for: 5m
labels:
severity: warning
category: edge
annotations:
summary: "LJWX Health Cloudflare Tunnel lost connector redundancy"
description: "Fewer than two Cloudflare edge connections have been active for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/edge-tunnel.md"
- alert: LjwxHealthCloudflaredMetricsMissing
expr: absent(up{namespace="ljwx-health",service="ljwx-health-cloudflared"})
for: 5m
labels:
severity: warning
category: observability
annotations:
summary: "LJWX Health Cloudflare Tunnel metrics are missing"
description: "Prometheus has not discovered cloudflared metrics for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/edge-tunnel.md"
- name: ljwx_health_message_chain
interval: 30s
rules:
- alert: LjwxHealthMessageOutboxBacklog
expr: max(ljwx_message_outbox_queue_size{namespace="ljwx-health",service="ljwx-boot"}) > 1000
for: 10m
labels:
severity: warning
category: message
service: ljwx-boot
annotations:
summary: "LJWX Health message outbox backlog is high"
description: "The message outbox has contained more than 1000 pending events for ten minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthMessageOutboxDeliveryFailureRate
expr: |
sum(rate(ljwx_message_outbox_delivery_total{namespace="ljwx-health",service="ljwx-boot",status="failure"}[5m]))
/
clamp_min(sum(rate(ljwx_message_outbox_delivery_total{namespace="ljwx-health",service="ljwx-boot"}[5m])), 0.001) > 0.05
and
sum(rate(ljwx_message_outbox_delivery_total{namespace="ljwx-health",service="ljwx-boot"}[5m])) > 0
for: 10m
labels:
severity: critical
category: message
service: ljwx-boot
annotations:
summary: "LJWX Health message outbox delivery is failing"
description: "More than 5% of message outbox deliveries have failed for ten minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- alert: LjwxHealthMessageResponseLatencyHigh
expr: |
histogram_quantile(
0.95,
sum(rate(ljwx_message_response_latency_seconds_bucket{namespace="ljwx-health",service="ljwx-boot"}[10m])) by (le)
) > 300
for: 15m
labels:
severity: warning
category: message
service: ljwx-boot
annotations:
summary: "LJWX Health message response P95 is above five minutes"
description: "The send-to-response P95 has remained above 300 seconds for fifteen minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/service-degradation.md"
- name: ljwx_health_storage_capacity
interval: 30s
rules:
- alert: LjwxHealthArchiveVerificationFailed
expr: sum(increase(health_archive_failed_total{namespace="ljwx-health"}[15m])) > 0
for: 1m
labels:
severity: critical
category: storage
pilot_action: stop
annotations:
summary: "Health archive verification or retention inspection failed"
description: "At least one archive or retention stage failed in the last fifteen minutes. Stop the full-collection pilot until the object and manifest are verified."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/storage-capacity-gate.md"
- alert: LjwxHealthArchiveDeleteBlocked
expr: sum(increase(health_archive_delete_blocked_total{namespace="ljwx-health"}[15m])) > 0
for: 1m
labels:
severity: warning
category: storage
pilot_action: hold
annotations:
summary: "Health retention deletion was blocked by the verified-manifest gate"
description: "Retention found rows without a matching VERIFIED archive manifest. Keep deletion disabled and investigate the archive range."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/storage-capacity-gate.md"
- alert: LjwxHealthArchiveBacklogHigh
expr: max(health_archive_pending_rows{namespace="ljwx-health"}) > 100000
for: 15m
labels:
severity: warning
category: storage
pilot_action: hold
annotations:
summary: "Health archive lifecycle backlog exceeds 100k rows"
description: "Eligible health rows have remained above the pilot capacity threshold for fifteen minutes. Do not expand the pilot."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/storage-capacity-gate.md"
- alert: LjwxHealthOlapLagStopThreshold
expr: max(health_olap_recon_lag_seconds{namespace="ljwx-health"}) > 300
for: 15m
labels:
severity: critical
category: storage
pilot_action: stop
annotations:
summary: "Health OLAP reconciliation lag exceeds five minutes"
description: "The RFC stop threshold has been exceeded for fifteen minutes. Disable the tenant pilot flag and return devices to the 60-second legacy policy."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/storage-capacity-gate.md"
- alert: LjwxHealthPostgresDeadlockDetected
expr: sum(increase(pg_stat_database_deadlocks{datname="ljwx_health"}[15m])) > 0
for: 1m
labels:
severity: critical
category: storage
pilot_action: stop
annotations:
summary: "PostgreSQL recorded a deadlock during the pilot window"
description: "Any database deadlock fails the #375 capacity gate. Stop burst collection and capture blocking-session evidence."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/storage-capacity-gate.md"
- alert: LjwxHealthStorageVolumeHigh
expr: |
1 - (
kubelet_volume_stats_available_bytes{namespace=~"infra|shared-base|ljwx-health",persistentvolumeclaim=~".*(postgres|timescaledb|kafka|redpanda|redis|minio|clickhouse).*"}
/
clamp_min(kubelet_volume_stats_capacity_bytes{namespace=~"infra|shared-base|ljwx-health",persistentvolumeclaim=~".*(postgres|timescaledb|kafka|redpanda|redis|minio|clickhouse).*"}, 1)
) > 0.80
for: 15m
labels:
severity: warning
category: storage
pilot_action: hold
annotations:
summary: "A health data platform volume is above 80%"
description: "{{ $labels.persistentvolumeclaim }} has crossed the expansion hold threshold."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/storage-capacity-gate.md"
- alert: LjwxHealthStorageVolumeCritical
expr: |
1 - (
kubelet_volume_stats_available_bytes{namespace=~"infra|shared-base|ljwx-health",persistentvolumeclaim=~".*(postgres|timescaledb|kafka|redpanda|redis|minio|clickhouse).*"}
/
clamp_min(kubelet_volume_stats_capacity_bytes{namespace=~"infra|shared-base|ljwx-health",persistentvolumeclaim=~".*(postgres|timescaledb|kafka|redpanda|redis|minio|clickhouse).*"}, 1)
) > 0.90
for: 5m
labels:
severity: critical
category: storage
pilot_action: stop
annotations:
summary: "A health data platform volume is above 90%"
description: "{{ $labels.persistentvolumeclaim }} has crossed the pilot stop threshold."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/storage-capacity-gate.md"
- name: ljwx_health_bigscreen_trial
interval: 30s
rules:
- record: ljwx_health:bigscreen_bff_qps:5m
expr: sum by (tenant) (rate(bigscreen_bff_requests_total{namespace="ljwx-health",service="ljwx-boot",endpoint=~"snapshot|realtime"}[5m]))
- record: ljwx_health:bigscreen_bff_success:ratio5m
expr: |
sum by (tenant) (rate(bigscreen_bff_requests_total{namespace="ljwx-health",service="ljwx-boot",endpoint=~"snapshot|realtime",result=~"success|not_modified"}[5m]))
/
clamp_min(sum by (tenant) (rate(bigscreen_bff_requests_total{namespace="ljwx-health",service="ljwx-boot",endpoint=~"snapshot|realtime"}[5m])), 0.001)
- record: ljwx_health:bigscreen_bff_latency_p95:5m
expr: |
histogram_quantile(
0.95,
sum by (tenant, le) (rate(bigscreen_bff_request_latency_seconds_bucket{namespace="ljwx-health",service="ljwx-boot",endpoint=~"snapshot|realtime"}[5m]))
)
- record: ljwx_health:bigscreen_bff_latency_p99:5m
expr: |
histogram_quantile(
0.99,
sum by (tenant, le) (rate(bigscreen_bff_request_latency_seconds_bucket{namespace="ljwx-health",service="ljwx-boot",endpoint=~"snapshot|realtime"}[5m]))
)
- record: ljwx_health:bigscreen_cache_hit:ratio5m
expr: |
sum by (tenant) (rate(bigscreen_snapshot_cache_access_total{namespace="ljwx-health",service="ljwx-boot",result="hit"}[5m]))
/
clamp_min(sum by (tenant) (rate(bigscreen_snapshot_cache_access_total{namespace="ljwx-health",service="ljwx-boot",result=~"hit|miss"}[5m])), 0.001)
- record: ljwx_health:bigscreen_snapshot_304:ratio5m
expr: |
sum by (tenant) (rate(bigscreen_bff_requests_total{namespace="ljwx-health",service="ljwx-boot",endpoint="snapshot",result="not_modified"}[5m]))
/
clamp_min(sum by (tenant) (rate(bigscreen_bff_requests_total{namespace="ljwx-health",service="ljwx-boot",endpoint="snapshot"}[5m])), 0.001)
- record: ljwx_health:bigscreen_snapshot_build_p95:5m
expr: |
histogram_quantile(
0.95,
sum by (tenant, le) (rate(bigscreen_snapshot_build_latency_seconds_bucket{namespace="ljwx-health",service="ljwx-boot"}[5m]))
)
- record: ljwx_health:bigscreen_kiosk_refresh_success:ratio5m
expr: |
sum by (tenant) (rate(bigscreen_kiosk_refresh_total{namespace="ljwx-health",service="ljwx-boot",result=~"success|not_modified"}[5m]))
/
clamp_min(sum by (tenant) (rate(bigscreen_kiosk_refresh_total{namespace="ljwx-health",service="ljwx-boot"}[5m])), 0.001)
- record: ljwx_health:bigscreen_kiosk_update_delay_p95:5m
expr: |
histogram_quantile(
0.95,
sum by (tenant, le) (rate(bigscreen_kiosk_last_update_delay_seconds_bucket{namespace="ljwx-health",service="ljwx-boot",result="success"}[5m]))
)
- record: ljwx_health:bigscreen_kiosk_recovery_p95:1h
expr: |
histogram_quantile(
0.95,
sum by (tenant, le) (rate(bigscreen_kiosk_recovery_latency_seconds_bucket{namespace="ljwx-health",service="ljwx-boot"}[1h]))
)
- record: ljwx_health:bigscreen_kiosk_heartbeat_age:seconds
expr: time() - max by (tenant) (bigscreen_kiosk_last_heartbeat_timestamp{namespace="ljwx-health",service="ljwx-boot"})
- alert: LjwxHealthBigscreenBffSuccessLow
expr: ljwx_health:bigscreen_bff_success:ratio5m < 0.995 and ljwx_health:bigscreen_bff_qps:5m > 0
for: 5m
labels:
severity: critical
category: bigscreen
annotations:
summary: "Bigscreen BFF success ratio is below 99.5%"
description: "Tenant {{ $labels.tenant }} has missed the trial success target for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenBffP95High
expr: ljwx_health:bigscreen_bff_latency_p95:5m > 0.5
for: 5m
labels:
severity: warning
category: bigscreen
annotations:
summary: "Bigscreen BFF P95 latency is above 500ms"
description: "Tenant {{ $labels.tenant }} has exceeded the P95 trial target for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenBffP99High
expr: ljwx_health:bigscreen_bff_latency_p99:5m > 1
for: 5m
labels:
severity: warning
category: bigscreen
annotations:
summary: "Bigscreen BFF P99 latency is above one second"
description: "Tenant {{ $labels.tenant }} has exceeded the P99 trial target for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenCacheHitLow
expr: |
ljwx_health:bigscreen_cache_hit:ratio5m < 0.70
and
sum by (tenant) (rate(bigscreen_snapshot_cache_access_total{namespace="ljwx-health",service="ljwx-boot",result=~"hit|miss"}[5m])) > 0
for: 10m
labels:
severity: warning
category: bigscreen
annotations:
summary: "Bigscreen snapshot cache hit ratio is below 70%"
description: "Tenant {{ $labels.tenant }} has missed the Redis cache hit target for ten minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenSnapshot304Low
expr: |
ljwx_health:bigscreen_snapshot_304:ratio5m < 0.30
and
sum by (tenant) (rate(bigscreen_bff_requests_total{namespace="ljwx-health",service="ljwx-boot",endpoint="snapshot"}[5m])) > 0
for: 15m
labels:
severity: warning
category: bigscreen
annotations:
summary: "Bigscreen snapshot 304 ratio is below 30%"
description: "Tenant {{ $labels.tenant }} has missed the HTTP cache reuse target for fifteen minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenKioskRefreshLow
expr: |
ljwx_health:bigscreen_kiosk_refresh_success:ratio5m < 0.995
and
sum by (tenant) (rate(bigscreen_kiosk_refresh_total{namespace="ljwx-health",service="ljwx-boot"}[5m])) > 0
for: 5m
labels:
severity: critical
category: bigscreen
annotations:
summary: "Bigscreen kiosk refresh success ratio is below 99.5%"
description: "Tenant {{ $labels.tenant }} has missed the kiosk refresh target for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenKioskUpdateDelayed
expr: ljwx_health:bigscreen_kiosk_update_delay_p95:5m > 2
for: 5m
labels:
severity: warning
category: bigscreen
annotations:
summary: "Bigscreen kiosk last-update delay is above two seconds"
description: "Tenant {{ $labels.tenant }} has exceeded the generatedAt-to-render target for five minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenKioskJsError
expr: sum by (tenant) (increase(bigscreen_kiosk_js_errors_total{namespace="ljwx-health",service="ljwx-boot"}[15m])) > 0
labels:
severity: critical
category: bigscreen
annotations:
summary: "Bigscreen kiosk reported a JavaScript error"
description: "Tenant {{ $labels.tenant }} reported at least one JavaScript error in fifteen minutes."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenKioskContinuousOffline
expr: |
ljwx_health:bigscreen_kiosk_heartbeat_age:seconds > 300
or
max by (tenant) (bigscreen_kiosk_browser_online{namespace="ljwx-health",service="ljwx-boot"}) == 0
or
absent(bigscreen_kiosk_last_heartbeat_timestamp{namespace="ljwx-health",service="ljwx-boot",tenant="1"})
for: 2m
labels:
severity: critical
category: bigscreen
annotations:
summary: "Bigscreen kiosk has been continuously offline"
description: "Tenant {{ $labels.tenant }} has no fresh online heartbeat."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
- alert: LjwxHealthBigscreenKioskRecoverySlow
expr: ljwx_health:bigscreen_kiosk_recovery_p95:1h > 30
for: 5m
labels:
severity: warning
category: bigscreen
annotations:
summary: "Bigscreen kiosk recovery P95 is above 30 seconds"
description: "Tenant {{ $labels.tenant }} has missed the offline recovery target."
runbook_url: "https://github.com/lingjingwanxiang/ljwx-health/blob/main/docs/04-operations/runbooks/bigscreen-observability.md"
+64
View File
@@ -0,0 +1,64 @@
#!/bin/bash
# Provision the restricted PostgreSQL runtime role without printing its secret.
set -euo pipefail
NAMESPACE="${NAMESPACE:-ljwx-health}"
SECRET_NAME="${SECRET_NAME:-ljwx-health-secret}"
POSTGRES_NAMESPACE="${POSTGRES_NAMESPACE:-infra}"
POSTGRES_POD="${POSTGRES_POD:-postgres-infra-postgresql-0}"
POSTGRES_DATABASE="${POSTGRES_DATABASE:-ljwx_health}"
APP_ROLE="${APP_ROLE:-ljwx_app}"
DB_HOST="${DB_HOST:-postgres-lb.infra.svc.cluster.local}"
DB_PORT="${DB_PORT:-5432}"
if [[ ! "${APP_ROLE}" =~ ^[a-z_][a-z0-9_]*$ ]]; then
echo "APP_ROLE must be a lowercase PostgreSQL identifier" >&2
exit 1
fi
if ! kubectl -n "${NAMESPACE}" get secret "${SECRET_NAME}" >/dev/null 2>&1; then
echo "missing secret ${NAMESPACE}/${SECRET_NAME}" >&2
exit 1
fi
app_password="${DB_APP_PASSWORD:-}"
if [[ -z "${app_password}" ]]; then
app_password="$(od -An -N32 -tx1 /dev/urandom | tr -d ' \n')"
fi
if [[ ! "${app_password}" =~ ^[0-9A-Za-z._~-]{32,128}$ ]]; then
echo "DB_APP_PASSWORD must contain 32-128 URL-safe characters" >&2
exit 1
fi
# The generated alphabet needs no SQL escaping. Feed the DDL through stdin so
# the password does not appear in argv, terminal output, or shell history.
printf "ALTER ROLE %s LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS PASSWORD '%s';\n" \
"${APP_ROLE}" "${app_password}" \
| kubectl -n "${POSTGRES_NAMESPACE}" exec -i "${POSTGRES_POD}" -- \
psql -U postgres -d "${POSTGRES_DATABASE}" -v ON_ERROR_STOP=1 -f - >/dev/null
# Prove TCP password authentication and role flags before publishing the key.
actual_flags="$({ printf '%s\n' "${app_password}"; } \
| kubectl -n "${POSTGRES_NAMESPACE}" exec -i "${POSTGRES_POD}" -- sh -ec '
IFS= read -r PGPASSWORD
export PGPASSWORD
psql --host="$1" --port="$2" --username="$3" --dbname="$4" \
--no-password --tuples-only --no-align \
--command="SELECT current_user || '\''|'\'' || rolsuper || '\''|'\'' || rolbypassrls FROM pg_roles WHERE rolname = current_user"
' sh "${DB_HOST}" "${DB_PORT}" "${APP_ROLE}" "${POSTGRES_DATABASE}")"
if [[ "${actual_flags}" != "${APP_ROLE}|false|false" ]]; then
echo "restricted-role verification failed" >&2
exit 1
fi
# Preserve every existing Secret key and merge only DB_APP_PASSWORD. The patch
# travels over stdin, so neither the plaintext nor base64 value appears in argv.
DB_APP_PASSWORD_B64="$(printf '%s' "${app_password}" | base64 | tr -d '\n')"
printf '{"data":{"DB_APP_PASSWORD":"%s"}}\n' "${DB_APP_PASSWORD_B64}" \
| kubectl -n "${NAMESPACE}" patch secret "${SECRET_NAME}" \
--type=merge --patch-file=/dev/stdin >/dev/null
unset DB_APP_PASSWORD_B64
unset app_password
echo "provisioned ${APP_ROLE}: LOGIN/NOSUPERUSER/NOBYPASSRLS; secret key updated without disclosure"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
# Rotate only the Sa-Token JWT signing key without reading or rewriting other Secret data.
set -euo pipefail
NAMESPACE="${NAMESPACE:-ljwx-health}"
SECRET_NAME="${SECRET_NAME:-ljwx-health-secret}"
if ! kubectl get namespace "${NAMESPACE}" >/dev/null 2>&1; then
echo "namespace ${NAMESPACE} does not exist" >&2
exit 1
fi
if ! kubectl get secret "${SECRET_NAME}" -n "${NAMESPACE}" >/dev/null 2>&1; then
echo "secret ${NAMESPACE}/${SECRET_NAME} does not exist" >&2
exit 1
fi
if [[ -z "${SA_TOKEN_JWT_SECRET:-}" ]]; then
SA_TOKEN_JWT_SECRET="$(head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')"
fi
if [[ "${#SA_TOKEN_JWT_SECRET}" -lt 32 ]]; then
echo "SA_TOKEN_JWT_SECRET must contain at least 32 characters" >&2
exit 1
fi
encoded="$(printf '%s' "${SA_TOKEN_JWT_SECRET}" | base64 | tr -d '\n')"
printf '{"data":{"SA_TOKEN_JWT_SECRET":"%s"}}' "${encoded}" \
| kubectl patch secret "${SECRET_NAME}" -n "${NAMESPACE}" --type=merge --patch-file=/dev/stdin >/dev/null
unset encoded SA_TOKEN_JWT_SECRET
echo "rotated ${NAMESPACE}/${SECRET_NAME} key SA_TOKEN_JWT_SECRET (value withheld)"
echo "existing Sa-Token sessions become invalid after ljwx-boot rolls out"
+1 -1
View File
@@ -11,7 +11,7 @@ spec:
type: ClusterIP
ports:
- name: http
port: 5001
port: 5225
targetPort: 5225
protocol: TCP
selector:
@@ -0,0 +1,33 @@
apiVersion: v1
kind: Service
metadata:
name: health-writer
namespace: ljwx-health
labels:
app: health-writer
component: health-shadow
spec:
selector:
app: health-writer
component: health-shadow
ports:
- name: http
port: 8080
targetPort: http
---
apiVersion: v1
kind: Service
metadata:
name: health-recon
namespace: ljwx-health
labels:
app: health-recon
component: health-shadow
spec:
selector:
app: health-recon
component: health-shadow
ports:
- name: http
port: 8080
targetPort: http
@@ -0,0 +1,28 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ljwx-health-shadow
namespace: ljwx-health
labels:
release: monitoring
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: health-shadow
spec:
namespaceSelector:
matchNames:
- ljwx-health
selector:
matchLabels:
component: health-shadow
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s
scrapeTimeout: 10s
relabelings:
- sourceLabels: [__meta_kubernetes_namespace]
targetLabel: namespace
- sourceLabels: [__meta_kubernetes_service_name]
targetLabel: service
- sourceLabels: [__meta_kubernetes_pod_name]
targetLabel: pod
+30
View File
@@ -0,0 +1,30 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ljwx-health-boot
namespace: ljwx-health
labels:
release: monitoring
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: backend
spec:
namespaceSelector:
matchNames:
- ljwx-health
selector:
matchLabels:
app: ljwx-boot
component: backend
endpoints:
- port: http
path: /actuator/prometheus
interval: 30s
scrapeTimeout: 10s
honorLabels: false
relabelings:
- sourceLabels: [__meta_kubernetes_namespace]
targetLabel: namespace
- sourceLabels: [__meta_kubernetes_service_name]
targetLabel: service
- sourceLabels: [__meta_kubernetes_pod_name]
targetLabel: pod
@@ -0,0 +1,61 @@
CREATE DATABASE IF NOT EXISTS ljwx_health;
USE ljwx_health;
CREATE TABLE IF NOT EXISTS t_health_raw (
event_id String,
customer_id UInt64,
org_id UInt64,
user_id UInt64,
device_sn String,
ts DateTime64(3, 'Asia/Shanghai'),
heart_rate Nullable(Int16),
blood_oxygen Nullable(Int16),
temperature Nullable(Float64),
pressure_high Nullable(Int16),
pressure_low Nullable(Int16),
stress Nullable(Int16),
step Nullable(Int32),
distance Nullable(Float64),
calorie Nullable(Float64),
version UInt64 DEFAULT 1,
vendor LowCardinality(String) DEFAULT '',
source_adapter LowCardinality(String) DEFAULT '',
precision_tier LowCardinality(String) DEFAULT '',
ingested_at DateTime64(3, 'UTC') DEFAULT now64(3)
)
ENGINE = ReplacingMergeTree(version)
PARTITION BY toYYYYMM(ts)
ORDER BY (customer_id, event_id)
TTL toDateTime(ts) + INTERVAL 365 DAY DELETE;
ALTER TABLE t_health_raw ADD COLUMN IF NOT EXISTS customer_id UInt64 AFTER event_id;
ALTER TABLE t_health_raw ADD COLUMN IF NOT EXISTS ingested_at DateTime64(3, 'UTC') DEFAULT now64(3);
ALTER TABLE t_health_raw ADD COLUMN IF NOT EXISTS vendor LowCardinality(String) DEFAULT '';
ALTER TABLE t_health_raw ADD COLUMN IF NOT EXISTS source_adapter LowCardinality(String) DEFAULT '';
ALTER TABLE t_health_raw ADD COLUMN IF NOT EXISTS precision_tier LowCardinality(String) DEFAULT '';
ALTER TABLE t_health_raw MODIFY COLUMN ts DateTime64(3, 'Asia/Shanghai');
-- ClickHouse 24.3 does not support CREATE OR REPLACE VIEW. Rebuild this
-- derived, stateless view idempotently while preserving the raw fact table.
DROP VIEW IF EXISTS t_health_raw_business;
CREATE VIEW t_health_raw_business AS
SELECT
customer_id,
event_id,
argMax(org_id, version) AS org_id,
argMax(user_id, version) AS user_id,
argMax(device_sn, version) AS device_sn,
argMax(ts, version) AS ts,
argMax(heart_rate, version) AS heart_rate,
argMax(blood_oxygen, version) AS blood_oxygen,
argMax(temperature, version) AS temperature,
argMax(pressure_high, version) AS pressure_high,
argMax(pressure_low, version) AS pressure_low,
argMax(stress, version) AS stress,
argMax(step, version) AS step,
argMax(distance, version) AS distance,
argMax(calorie, version) AS calorie,
max(version) AS latest_version,
max(ingested_at) AS ingested_at
FROM t_health_raw
GROUP BY customer_id, event_id;
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
# Copy protected ClickHouse credentials without decoding or printing them.
set -euo pipefail
set +x
SOURCE_NAMESPACE="${SOURCE_NAMESPACE:-infra}"
SOURCE_SECRET="${SOURCE_SECRET:-clickhouse-secret}"
TARGET_NAMESPACE="${TARGET_NAMESPACE:-ljwx-health}"
TARGET_SECRET="${TARGET_SECRET:-ljwx-health-clickhouse-secret}"
KUBECTL="${KUBECTL:-kubectl}"
command -v "$KUBECTL" >/dev/null 2>&1 || {
echo "kubectl executable not found: $KUBECTL" >&2
exit 1
}
command -v jq >/dev/null 2>&1 || {
echo "jq is required" >&2
exit 1
}
source_json="$($KUBECTL -n "$SOURCE_NAMESPACE" get secret "$SOURCE_SECRET" -o json)"
source_user="$(printf '%s' "$source_json" | jq -r '.data.username // empty')"
source_password="$(printf '%s' "$source_json" | jq -r '.data.password // empty')"
if [ -z "$source_user" ] || [ -z "$source_password" ]; then
echo "ClickHouse source Secret is missing username/password" >&2
exit 1
fi
target_patch="$(jq -cn \
--arg user "$source_user" \
--arg password "$source_password" \
'{data:{CLICKHOUSE_USER:$user,CLICKHOUSE_PASSWORD:$password}}')"
if $KUBECTL -n "$TARGET_NAMESPACE" get secret "$TARGET_SECRET" >/dev/null 2>&1; then
printf '%s' "$target_patch" | $KUBECTL -n "$TARGET_NAMESPACE" patch secret "$TARGET_SECRET" \
--type=merge --patch-file=/dev/stdin >/dev/null
else
printf '%s' "$target_patch" | jq \
--arg name "$TARGET_SECRET" \
--arg namespace "$TARGET_NAMESPACE" \
'{apiVersion:"v1",kind:"Secret",metadata:{name:$name,namespace:$namespace},type:"Opaque",data:.data}' \
| $KUBECTL create -f - >/dev/null
fi
$KUBECTL -n "$TARGET_NAMESPACE" annotate secret "$TARGET_SECRET" \
kubectl.kubernetes.io/last-applied-configuration- >/dev/null 2>&1 || true
target_json="$($KUBECTL -n "$TARGET_NAMESPACE" get secret "$TARGET_SECRET" -o json)"
target_user="$(printf '%s' "$target_json" | jq -r '.data.CLICKHOUSE_USER // empty')"
target_password="$(printf '%s' "$target_json" | jq -r '.data.CLICKHOUSE_PASSWORD // empty')"
if [ "$target_user" != "$source_user" ] || [ "$target_password" != "$source_password" ]; then
echo "ClickHouse credential verification failed" >&2
exit 1
fi
unset source_json source_user source_password target_patch target_json target_user target_password
echo "ClickHouse credentials synchronized and verified without disclosure"
+62
View File
@@ -0,0 +1,62 @@
#!/bin/bash
# Align ljwx-health runtime OSS credentials with the in-cluster MinIO source Secret.
set -euo pipefail
set +x
SOURCE_NAMESPACE="${SOURCE_NAMESPACE:-infra}"
SOURCE_SECRET="${SOURCE_SECRET:-minio-credentials}"
SOURCE_ACCESS_KEY="${SOURCE_ACCESS_KEY:-root-user}"
SOURCE_SECRET_KEY="${SOURCE_SECRET_KEY:-root-password}"
TARGET_NAMESPACE="${TARGET_NAMESPACE:-ljwx-health}"
TARGET_SECRET="${TARGET_SECRET:-ljwx-health-secret}"
CLEAN_SOURCE_LAST_APPLIED="${CLEAN_SOURCE_LAST_APPLIED:-true}"
KUBECTL="${KUBECTL:-kubectl}"
command -v "$KUBECTL" >/dev/null 2>&1 || {
echo "kubectl executable not found: $KUBECTL" >&2
exit 1
}
command -v jq >/dev/null 2>&1 || {
echo "jq is required" >&2
exit 1
}
source_json="$($KUBECTL -n "$SOURCE_NAMESPACE" get secret "$SOURCE_SECRET" -o json)"
source_access="$(printf '%s' "$source_json" | jq -r --arg key "$SOURCE_ACCESS_KEY" '.data[$key] // empty')"
source_secret="$(printf '%s' "$source_json" | jq -r --arg key "$SOURCE_SECRET_KEY" '.data[$key] // empty')"
if [ -z "$source_access" ] || [ -z "$source_secret" ]; then
echo "MinIO source Secret is missing the configured credential keys" >&2
exit 1
fi
$KUBECTL -n "$TARGET_NAMESPACE" get secret "$TARGET_SECRET" >/dev/null
jq -cn \
--arg access "$source_access" \
--arg secret "$source_secret" \
'{data:{OSS_ACCESS_KEY:$access,OSS_SECRET_KEY:$secret}}' \
| $KUBECTL -n "$TARGET_NAMESPACE" patch secret "$TARGET_SECRET" \
--type=merge --patch-file=/dev/stdin >/dev/null
# `kubectl apply` stores base64 Secret values in this annotation. Runtime Secrets
# are intentionally managed by merge patch, so the annotation is both unnecessary
# and an avoidable disclosure surface.
$KUBECTL -n "$TARGET_NAMESPACE" annotate secret "$TARGET_SECRET" \
kubectl.kubernetes.io/last-applied-configuration- >/dev/null 2>&1 || true
if [ "$CLEAN_SOURCE_LAST_APPLIED" = "true" ]; then
$KUBECTL -n "$SOURCE_NAMESPACE" annotate secret "$SOURCE_SECRET" \
kubectl.kubernetes.io/last-applied-configuration- >/dev/null 2>&1 || true
fi
target_json="$($KUBECTL -n "$TARGET_NAMESPACE" get secret "$TARGET_SECRET" -o json)"
target_access="$(printf '%s' "$target_json" | jq -r '.data.OSS_ACCESS_KEY // empty')"
target_secret="$(printf '%s' "$target_json" | jq -r '.data.OSS_SECRET_KEY // empty')"
if [ "$target_access" != "$source_access" ] || [ "$target_secret" != "$source_secret" ]; then
echo "MinIO credential verification failed" >&2
exit 1
fi
unset source_json source_access source_secret target_json target_access target_secret
echo "MinIO credentials synchronized and verified without disclosure"
@@ -0,0 +1,16 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-admin
namespace: ljwx-health
spec:
template:
metadata:
annotations:
ops.ljwx.io/migration-stage: contabo-canary
spec:
containers:
- name: ljwx-admin
env:
- name: VITE_API_URL
value: https://health-admin-contabo-canary.lingjingwanxiang.cn/api
@@ -0,0 +1,16 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-bigscreen
namespace: ljwx-health
spec:
template:
metadata:
annotations:
ops.ljwx.io/migration-stage: contabo-canary
spec:
containers:
- name: ljwx-bigscreen
env:
- name: BIGSCREEN_CUTOVER_ADMIN_BASE_URL
value: https://health-dashboard-contabo-canary.lingjingwanxiang.cn
@@ -0,0 +1,22 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-boot
namespace: ljwx-health
spec:
template:
metadata:
annotations:
ops.ljwx.io/migration-stage: contabo-canary
spec:
containers:
- name: ljwx-boot
env:
- name: LJWX_HEALTH_OUTBOX_KAFKA_ENABLED
value: "false"
- name: LJWX_HEALTH_OUTBOX_KAFKA_ALLOWED_CUSTOMER_IDS
value: ""
- name: LJWX_HEALTH_REALTIME_SCORE_ENABLED
value: "false"
- name: LJWX_HEALTH_REALTIME_SCORE_CUSTOMERS
value: ""
@@ -0,0 +1,69 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-traefik
namespace: ljwx-health
spec:
$patch: replace
ingressClassName: traefik
tls:
- hosts:
- health-admin-contabo-canary.lingjingwanxiang.cn
- health-dashboard-contabo-canary.lingjingwanxiang.cn
secretName: ljwx-health-contabo-canary-tls
rules:
- host: health-admin-contabo-canary.lingjingwanxiang.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-admin
port:
number: 80
- host: health-dashboard-contabo-canary.lingjingwanxiang.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-admin
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-api-traefik
namespace: ljwx-health
spec:
$patch: replace
ingressClassName: traefik
tls:
- hosts:
- health-admin-contabo-canary.lingjingwanxiang.cn
- health-dashboard-contabo-canary.lingjingwanxiang.cn
secretName: ljwx-health-contabo-canary-tls
rules:
- host: health-admin-contabo-canary.lingjingwanxiang.cn
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
- host: health-dashboard-contabo-canary.lingjingwanxiang.cn
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
@@ -0,0 +1,213 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-health
resources:
- ../contabo-prod
patches:
- target:
version: v1
kind: ConfigMap
namespace: ljwx-health
name: ljwx-health-cloudflared-config
patch: |-
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-health-cloudflared-config
namespace: ljwx-health
$patch: delete
- target:
group: apps
version: v1
kind: Deployment
namespace: ljwx-health
name: ljwx-health-cloudflared
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-health-cloudflared
namespace: ljwx-health
$patch: delete
- target:
version: v1
kind: Service
namespace: ljwx-health
name: ljwx-health-cloudflared
patch: |-
apiVersion: v1
kind: Service
metadata:
name: ljwx-health-cloudflared
namespace: ljwx-health
$patch: delete
- target:
group: policy
version: v1
kind: PodDisruptionBudget
namespace: ljwx-health
name: ljwx-health-cloudflared
patch: |-
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: ljwx-health-cloudflared
namespace: ljwx-health
$patch: delete
- target:
group: monitoring.coreos.com
version: v1
kind: ServiceMonitor
namespace: ljwx-health
name: ljwx-health-cloudflared
patch: |-
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ljwx-health-cloudflared
namespace: ljwx-health
$patch: delete
- target:
group: networking.k8s.io
version: v1
kind: NetworkPolicy
namespace: ljwx-health
name: ljwx-health-cloudflared
patch: |-
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ljwx-health-cloudflared
namespace: ljwx-health
$patch: delete
- target:
group: batch
version: v1
kind: Job
namespace: ljwx-health
name: health-shadow-kafka-init
patch: |-
apiVersion: batch/v1
kind: Job
metadata:
name: health-shadow-kafka-init
namespace: ljwx-health
$patch: delete
- target:
group: batch
version: v1
kind: Job
namespace: ljwx-health
name: health-shadow-clickhouse-init
patch: |-
apiVersion: batch/v1
kind: Job
metadata:
name: health-shadow-clickhouse-init
namespace: ljwx-health
$patch: delete
- target:
group: apps
version: v1
kind: Deployment
namespace: ljwx-health
name: health-writer
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: health-writer
namespace: ljwx-health
$patch: delete
- target:
group: apps
version: v1
kind: Deployment
namespace: ljwx-health
name: health-recon
patch: |-
apiVersion: apps/v1
kind: Deployment
metadata:
name: health-recon
namespace: ljwx-health
$patch: delete
- target:
version: v1
kind: Service
namespace: ljwx-health
name: health-writer
patch: |-
apiVersion: v1
kind: Service
metadata:
name: health-writer
namespace: ljwx-health
$patch: delete
- target:
version: v1
kind: Service
namespace: ljwx-health
name: health-recon
patch: |-
apiVersion: v1
kind: Service
metadata:
name: health-recon
namespace: ljwx-health
$patch: delete
- target:
group: monitoring.coreos.com
version: v1
kind: ServiceMonitor
namespace: ljwx-health
name: ljwx-health-shadow
patch: |-
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: ljwx-health-shadow
namespace: ljwx-health
$patch: delete
- target:
group: monitoring.coreos.com
version: v1
kind: PrometheusRule
namespace: ljwx-health
name: ljwx-health-shadow
patch: |-
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: ljwx-health-shadow
namespace: ljwx-health
$patch: delete
- target:
group: monitoring.coreos.com
version: v1
kind: PrometheusRule
namespace: ljwx-health
name: ljwx-health-slo
patch: |-
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: ljwx-health-slo
namespace: ljwx-health
$patch: delete
- target:
version: v1
kind: ConfigMap
namespace: ljwx-health
name: ljwx-health-shadow-clickhouse-schema
patch: |-
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-health-shadow-clickhouse-schema
namespace: ljwx-health
$patch: delete
- path: ingress-canary-patch.yaml
- path: deployment-boot-staging.yaml
- path: deployment-admin-staging.yaml
- path: deployment-bigscreen-staging.yaml
@@ -0,0 +1,22 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-health-cloudflared-config
data:
config.yaml: |
tunnel: 70479f3e-caaf-4528-b713-413cc27e8ad0
credentials-file: /etc/cloudflared/credentials/credentials.json
metrics: 0.0.0.0:2000
loglevel: info
ingress:
- hostname: health-admin.omniverseai.net
service: http://traefik.kube-system.svc.cluster.local:80
originRequest:
httpHostHeader: health-admin.omniverseai.net
connectTimeout: 10s
- hostname: health-dashboard.omniverseai.net
service: http://traefik.kube-system.svc.cluster.local:80
originRequest:
httpHostHeader: health-dashboard.omniverseai.net
connectTimeout: 10s
- service: http_status:404
@@ -0,0 +1,37 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ljwx-health-cloudflared
spec:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
ports:
- protocol: TCP
port: 80
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ports:
- protocol: TCP
port: 7844
- protocol: UDP
port: 7844
@@ -0,0 +1,55 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-api-traefik
annotations:
cert-manager.io/cluster-issuer: dnspod-letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: ljwx-health-health-api-chain@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- health-admin.lingjingwanxiang.cn
- health-dashboard.lingjingwanxiang.cn
secretName: ljwx-health-contabo-prod-tls
rules:
- host: health-admin.lingjingwanxiang.cn
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
- host: health-dashboard.lingjingwanxiang.cn
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
- host: health-admin.omniverseai.net
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
- host: health-dashboard.omniverseai.net
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
@@ -0,0 +1,28 @@
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ljwx-health-ingress
namespace: ljwx-health
spec:
ingress:
- from:
- podSelector: {}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: infra
podSelector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/component: controller
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
app.kubernetes.io/name: traefik
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: observability
podSelector:
matchLabels:
app.kubernetes.io/name: prometheus
@@ -0,0 +1,55 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-traefik
annotations:
cert-manager.io/cluster-issuer: dnspod-letsencrypt
traefik.ingress.kubernetes.io/router.middlewares: ljwx-health-health-cors@kubernetescrd
spec:
ingressClassName: traefik
tls:
- hosts:
- health-admin.lingjingwanxiang.cn
- health-dashboard.lingjingwanxiang.cn
secretName: ljwx-health-contabo-prod-tls
rules:
- host: health-admin.lingjingwanxiang.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-admin
port:
number: 80
- host: health-dashboard.lingjingwanxiang.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-admin
port:
number: 80
- host: health-admin.omniverseai.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-admin
port:
number: 80
- host: health-dashboard.omniverseai.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-admin
port:
number: 80
@@ -0,0 +1,38 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-health
resources:
- ../k3s-eu
- middleware-traefik.yaml
- ingress-traefik.yaml
- ingress-api-traefik.yaml
patches:
- target:
group: networking.k8s.io
version: v1
kind: Ingress
namespace: ljwx-health
name: ljwx-health-ingress
patch: |-
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-ingress
namespace: ljwx-health
$patch: delete
- target:
group: networking.k8s.io
version: v1
kind: Ingress
namespace: ljwx-health
name: ljwx-health-api-ingress
patch: |-
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-api-ingress
namespace: ljwx-health
$patch: delete
- path: cloudflared-config-patch.yaml
- path: cloudflared-networkpolicy-patch.yaml
- path: ingress-networkpolicy-patch.yaml
@@ -0,0 +1,52 @@
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: health-cors
spec:
headers:
accessControlAllowCredentials: true
accessControlAllowHeaders:
- DNT
- X-CustomHeader
- Keep-Alive
- User-Agent
- X-Requested-With
- If-Modified-Since
- Cache-Control
- Content-Type
- Authorization
accessControlAllowMethods:
- GET
- POST
- PUT
- PATCH
- DELETE
- OPTIONS
accessControlAllowOriginList:
- https://health-admin.lingjingwanxiang.cn
- https://health-dashboard.lingjingwanxiang.cn
- https://health-admin.omniverseai.net
- https://health-dashboard.omniverseai.net
- https://health-admin-contabo-canary.lingjingwanxiang.cn
- https://health-dashboard-contabo-canary.lingjingwanxiang.cn
accessControlMaxAge: 600
addVaryHeader: true
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: health-strip-api
spec:
stripPrefix:
prefixes:
- /api
---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: health-api-chain
spec:
chain:
middlewares:
- name: health-strip-api
- name: health-cors
@@ -0,0 +1,15 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-bigscreen
namespace: ljwx-health
spec:
template:
spec:
containers:
- name: ljwx-bigscreen
env:
- name: BIGSCREEN_CUTOVER_MODE
value: allowlist
- name: BIGSCREEN_CUTOVER_TENANTS
value: "1"
@@ -0,0 +1,30 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-boot
namespace: ljwx-health
spec:
template:
metadata:
annotations:
ops.ljwx.io/config-revision: health-shadow-realtime-score-tenant-1-v2
spec:
containers:
- name: ljwx-boot
env:
- name: LJWX_HEALTH_OUTBOX_KAFKA_ENABLED
value: "true"
- name: LJWX_HEALTH_OUTBOX_KAFKA_ALLOWED_CUSTOMER_IDS
value: "1"
- name: LJWX_HEALTH_OUTBOX_KAFKA_ALLOW_ALL_TENANTS
value: "false"
- name: LJWX_HEALTH_OUTBOX_KAFKA_BOOTSTRAP_SERVERS
value: kafka-cluster-kafka-bootstrap.shared-base.svc.cluster.local:9092
- name: LJWX_HEALTH_REALTIME_SCORE_ENABLED
value: "true"
- name: LJWX_HEALTH_REALTIME_SCORE_CUSTOMERS
value: "1"
- name: LJWX_HEALTH_REALTIME_SCORE_ROLLOUT_PERCENTAGE
value: "0"
- name: LJWX_HEALTH_REALTIME_SCORE_FLUSH_INTERVAL_MS
value: "60000"
@@ -3,13 +3,28 @@ kind: Kustomization
namespace: ljwx-health
resources:
- ../../base
- ../../schema-bundle
- schema-release.yaml
patches:
- path: deployment-bigscreen-cutover.yaml
- path: deployment-boot-shadow-canary.yaml
- path: schema-job-release-patch.yaml
images:
- name: harbor.omniverseai.net/ljwx-health/ljwx-boot
newName: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-boot
digest: sha256:898aa553fa95e59bc94c8918f8975c994cf657164c4b534563b89a5f365cd1bc
newName: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-boot
digest: sha256:c9bcea82ef9da03b3315a5550cb4cbfa456f71d6267e492f304aa085cb7f50ba
- name: harbor.omniverseai.net/ljwx-health/ljwx-admin
newName: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-admin
digest: sha256:901465c8d0fd55c802670d8e0a348f1eeca2d69b908dc21aaf124749ebc63540
newName: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-admin
digest: sha256:f212cab3e2c4cfc9161443d90bfc637137686d62b8417074365b3f8dc623c8e3
- name: harbor.omniverseai.net/ljwx-health/ljwx-bigscreen
newName: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-bigscreen
digest: sha256:1ff48cfec957bec053cf25a22f28edc58d0a229e4a4a86211ba3bc6f13eae753
newName: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-bigscreen
digest: sha256:a3a5e2141d74380b0b85408b855692f0d61e56d108e18023e9551fed5aaf3657
- name: harbor.omniverseai.net/ljwx-health/health-writer
newName: ghcr.io/lingjingwanxiang/ljwx-health/health-writer
digest: sha256:8bc97dd8af74fb768cf47b818c000f9d4b554f83871a0fb4bbf271c548ead6dd
- name: harbor.omniverseai.net/ljwx-health/health-recon
newName: ghcr.io/lingjingwanxiang/ljwx-health/health-recon
digest: sha256:35cd383ff02f258fe6f81e924371fffc0be19b49bd711c5ecf703ce6e76071f2
- name: ghcr.io/lingjingwanxiang/ljwx-health/schema-runner
newName: ghcr.io/lingjingwanxiang/ljwx-health/schema-runner
digest: sha256:0d077562a841fa881fa5f4454cea64defa3f345582f96b00c94b7adffdd6c11c
@@ -0,0 +1,62 @@
apiVersion: delivery.lingjingwanxiang.cn/v1alpha1
kind: LJWXHealthPromotion
receiptDigest: sha256:06ee4823163efd0681d042a94eea9958f0b50ea17f0d6517b25b20d65620e2b3
source:
repository: lingjingwanxiang/ljwx-health
sha: ee2e655bd137cf83e5f43c638009ed1a3a0fc839
tag: ljwx-health-release-ee2e655bd137cf83e5f43c638009ed1a3a0fc839-run-30660382221-attempt-1
manifestDigest: sha256:4cb7ffe007a4f8944c910b415e667aa474e792a56e1eb3438bb7db94cf2abe68
releaseManifest:
contentDigest: sha256:4cb7ffe007a4f8944c910b415e667aa474e792a56e1eb3438bb7db94cf2abe68
ociRef: ghcr.io/lingjingwanxiang/ljwx-health/release-manifest@sha256:6b47536d82a9b4a821aa34968ad6f98dd709f50baeb7649465923d563d1ecf55
ociDigest: sha256:6b47536d82a9b4a821aa34968ad6f98dd709f50baeb7649465923d563d1ecf55
schema:
version: 3.1.72
manifestDigest: sha256:e4d7195bd821fe6765234499fd313064ee2c591f7169d21a380c234b7c3f76d0
bundleDigest: sha256:783efd481f9af5d6f9459f53ed20a5d8758ab96cfd25ce25b63814b6282cbfcd
bundleMediaType: application/vnd.ljwx.health.schema.bundle.v2+tar+gzip
expectedProductionFingerprint: sha256:565a8f1edf4b997ecfd66e6c3f06b33ebead4842a51d8fbb92d5b1f5f75d9ee8
canonicalProductionFingerprint: sha256:3440ae5ba527de93c5f1dae245c87552250c891c36476ff43dfa267173e95c12
adoption:
catalogComparison:
canonicalFingerprint: sha256:3440ae5ba527de93c5f1dae245c87552250c891c36476ff43dfa267173e95c12
evidenceDigest: sha256:290e7fac704fc4b706ed1449b1e979d0a0395383e91817328a9f4b54dad4f079
evidencePath: ljwx-boot/database/schema/adoption/production-k3s-eu-pg18.allowed-catalog-differences.json
environment: ljwx-health-k3s-eu
expectedPostFingerprint: sha256:565a8f1edf4b997ecfd66e6c3f06b33ebead4842a51d8fbb92d5b1f5f75d9ee8
expectedPreFingerprint: sha256:565a8f1edf4b997ecfd66e6c3f06b33ebead4842a51d8fbb92d5b1f5f75d9ee8
planDigest: sha256:f39b85d57258492ff0af6693268a9239af9cc6449ced1104673a006fa6c6e29e
planPath: ljwx-boot/database/schema/adoption/production-k3s-eu-pg18.json
profile: production-pg18
ociRef: ghcr.io/lingjingwanxiang/ljwx-health/schema-bundle@sha256:26f4ef828dde53804b50cc1bf6d5f01491ae6d149ef0606e53358d99bc4ef06b
ociDigest: sha256:26f4ef828dde53804b50cc1bf6d5f01491ae6d149ef0606e53358d99bc4ef06b
runnerImageRef: ghcr.io/lingjingwanxiang/ljwx-health/schema-runner@sha256:0d077562a841fa881fa5f4454cea64defa3f345582f96b00c94b7adffdd6c11c
signingIdentity: https://github.com/lingjingwanxiang/ljwx-workflow-templates/.github/workflows/release-ljwx-health.yml@fd260a24f92edef33f8b99c1047d5c8b80586223
signingIssuer: https://token.actions.githubusercontent.com
binding:
predicateDigest: sha256:73fe489662e0c8c741ba6703d06d957c3908312805df1f596a1136792e68723a
attestationDigest: sha256:1e4e5bfb9d168db75f5aa710dd9b999a9093dd0b0f102cbcbd190f4f7dd320f5
build:
runId: '30660382221'
runAttempt: '1'
components:
- name: ljwx-boot
image: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-boot
digest: sha256:c9bcea82ef9da03b3315a5550cb4cbfa456f71d6267e492f304aa085cb7f50ba
imageRef: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-boot@sha256:c9bcea82ef9da03b3315a5550cb4cbfa456f71d6267e492f304aa085cb7f50ba
- name: health-writer
image: ghcr.io/lingjingwanxiang/ljwx-health/health-writer
digest: sha256:8bc97dd8af74fb768cf47b818c000f9d4b554f83871a0fb4bbf271c548ead6dd
imageRef: ghcr.io/lingjingwanxiang/ljwx-health/health-writer@sha256:8bc97dd8af74fb768cf47b818c000f9d4b554f83871a0fb4bbf271c548ead6dd
- name: health-recon
image: ghcr.io/lingjingwanxiang/ljwx-health/health-recon
digest: sha256:35cd383ff02f258fe6f81e924371fffc0be19b49bd711c5ecf703ce6e76071f2
imageRef: ghcr.io/lingjingwanxiang/ljwx-health/health-recon@sha256:35cd383ff02f258fe6f81e924371fffc0be19b49bd711c5ecf703ce6e76071f2
- name: ljwx-admin
image: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-admin
digest: sha256:f212cab3e2c4cfc9161443d90bfc637137686d62b8417074365b3f8dc623c8e3
imageRef: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-admin@sha256:f212cab3e2c4cfc9161443d90bfc637137686d62b8417074365b3f8dc623c8e3
- name: ljwx-bigscreen
image: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-bigscreen
digest: sha256:a3a5e2141d74380b0b85408b855692f0d61e56d108e18023e9551fed5aaf3657
imageRef: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-bigscreen@sha256:a3a5e2141d74380b0b85408b855692f0d61e56d108e18023e9551fed5aaf3657
@@ -0,0 +1,15 @@
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-health-schema-bundle
namespace: ljwx-health
spec:
template:
spec:
containers:
- name: schema-bundle
envFrom:
- configMapRef:
name: ljwx-health-config
- configMapRef:
name: ljwx-health-schema-release-783efd481f9af5d6
@@ -0,0 +1,34 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-health-schema-release-783efd481f9af5d6
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: '-3'
labels:
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: schema-release
immutable: true
data:
sourceSha: ee2e655bd137cf83e5f43c638009ed1a3a0fc839
releaseManifestDigest: sha256:4cb7ffe007a4f8944c910b415e667aa474e792a56e1eb3438bb7db94cf2abe68
releaseManifestOciRef: ghcr.io/lingjingwanxiang/ljwx-health/release-manifest@sha256:6b47536d82a9b4a821aa34968ad6f98dd709f50baeb7649465923d563d1ecf55
schemaVersion: 3.1.72
schemaManifestDigest: sha256:e4d7195bd821fe6765234499fd313064ee2c591f7169d21a380c234b7c3f76d0
schemaBundleDigest: sha256:783efd481f9af5d6f9459f53ed20a5d8758ab96cfd25ce25b63814b6282cbfcd
schemaBundleMediaType: application/vnd.ljwx.health.schema.bundle.v2+tar+gzip
schemaOciRef: ghcr.io/lingjingwanxiang/ljwx-health/schema-bundle@sha256:26f4ef828dde53804b50cc1bf6d5f01491ae6d149ef0606e53358d99bc4ef06b
schemaRunnerImageRef: ghcr.io/lingjingwanxiang/ljwx-health/schema-runner@sha256:0d077562a841fa881fa5f4454cea64defa3f345582f96b00c94b7adffdd6c11c
expectedProductionFingerprint: sha256:565a8f1edf4b997ecfd66e6c3f06b33ebead4842a51d8fbb92d5b1f5f75d9ee8
canonicalProductionFingerprint: sha256:3440ae5ba527de93c5f1dae245c87552250c891c36476ff43dfa267173e95c12
adoptionPlanPath: ljwx-boot/database/schema/adoption/production-k3s-eu-pg18.json
adoptionPlanDigest: sha256:f39b85d57258492ff0af6693268a9239af9cc6449ced1104673a006fa6c6e29e
adoptionEvidencePath: ljwx-boot/database/schema/adoption/production-k3s-eu-pg18.allowed-catalog-differences.json
adoptionEvidenceDigest: sha256:290e7fac704fc4b706ed1449b1e979d0a0395383e91817328a9f4b54dad4f079
adoptionExpectedPreFingerprint: sha256:565a8f1edf4b997ecfd66e6c3f06b33ebead4842a51d8fbb92d5b1f5f75d9ee8
adoptionExpectedPostFingerprint: sha256:565a8f1edf4b997ecfd66e6c3f06b33ebead4842a51d8fbb92d5b1f5f75d9ee8
signingIdentity: https://github.com/lingjingwanxiang/ljwx-workflow-templates/.github/workflows/release-ljwx-health.yml@fd260a24f92edef33f8b99c1047d5c8b80586223
signingIssuer: https://token.actions.githubusercontent.com
bindingPredicateDigest: sha256:73fe489662e0c8c741ba6703d06d957c3908312805df1f596a1136792e68723a
bindingAttestationDigest: sha256:1e4e5bfb9d168db75f5aa710dd9b999a9093dd0b0f102cbcbd190f4f7dd320f5
@@ -0,0 +1,374 @@
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-health-schema-bundle
namespace: ljwx-health
labels:
app: ljwx-health
component: schema-bundle
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "-2"
spec:
backoffLimit: 0
ttlSecondsAfterFinished: 604800
template:
metadata:
labels:
app: ljwx-health
component: schema-bundle
spec:
restartPolicy: Never
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-pull
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: schema-bundle
image: ghcr.io/lingjingwanxiang/ljwx-health/schema-runner:unpromoted
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- -ceu
- |
export HOME=/work/home
export DOCKER_CONFIG=/registry
mkdir -p "${HOME}" /work/release /work/schema /work/bundle
for name in \
sourceSha releaseManifestDigest releaseManifestOciRef \
schemaVersion schemaManifestDigest schemaBundleDigest \
schemaBundleMediaType schemaOciRef schemaRunnerImageRef \
expectedProductionFingerprint canonicalProductionFingerprint \
adoptionPlanPath adoptionPlanDigest adoptionEvidencePath \
adoptionEvidenceDigest adoptionExpectedPreFingerprint \
adoptionExpectedPostFingerprint signingIdentity signingIssuer \
bindingPredicateDigest bindingAttestationDigest; do
value="$(printenv "${name}")"
test -n "${value}"
done
case "${schemaRunnerImageRef}" in
ghcr.io/lingjingwanxiang/ljwx-health/schema-runner@sha256:*) ;;
*) exit 1 ;;
esac
cosign verify \
--certificate-identity "${signingIdentity}" \
--certificate-oidc-issuer "${signingIssuer}" \
"${releaseManifestOciRef}" \
>/work/release-signature-verification.json
oras pull "${releaseManifestOciRef}" --output /work/release
python3 - "${releaseManifestDigest}" \
/work/release/release-manifest-core.json <<'PY'
import hashlib
import json
import sys
from pathlib import Path
expected = sys.argv[1]
payload = json.loads(Path(sys.argv[2]).read_text())
actual = payload.pop("manifestDigest", None)
canonical = json.dumps(
payload,
ensure_ascii=True,
sort_keys=True,
separators=(",", ":"),
).encode()
computed = "sha256:" + hashlib.sha256(canonical).hexdigest()
if actual != expected or computed != expected:
raise SystemExit(
"signed Release core content digest is invalid"
)
PY
cosign verify \
--certificate-identity "${signingIdentity}" \
--certificate-oidc-issuer "${signingIssuer}" \
"${schemaOciRef}" \
>/work/schema-signature-verification.json
cosign verify-attestation \
--type custom \
--certificate-identity "${signingIdentity}" \
--certificate-oidc-issuer "${signingIssuer}" \
"${schemaOciRef}" \
>/work/schema-binding-verification.json
cosign verify-attestation \
--type spdxjson \
--certificate-identity "${signingIdentity}" \
--certificate-oidc-issuer "${signingIssuer}" \
"${schemaOciRef}" \
>/work/schema-sbom-verification.json
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity "${signingIdentity}" \
--certificate-oidc-issuer "${signingIssuer}" \
"${schemaOciRef}" \
>/work/schema-provenance-verification.json
cat >/work/schema-release-binding.predicate.json <<JSON
{
"schemaVersion": 1,
"kind": "LJWXHealthSchemaReleaseBinding",
"schemaSubject": "${schemaOciRef}",
"releaseManifest": {
"contentDigest": "${releaseManifestDigest}",
"ociRef": "${releaseManifestOciRef}",
"ociDigest": "${releaseManifestOciRef##*@}"
},
"source": {
"repository": "lingjingwanxiang/ljwx-health",
"sha": "${sourceSha}"
}
}
JSON
actual_predicate_digest="$(
sha256sum /work/schema-release-binding.predicate.json \
| awk '{print "sha256:" $1}'
)"
test "${actual_predicate_digest}" = "${bindingPredicateDigest}"
python3 - /work/schema-release-binding.predicate.json \
/work/schema-binding-verification.json \
"${schemaOciRef##*@}" <<'PY'
import base64
import json
import sys
from pathlib import Path
expected = json.loads(Path(sys.argv[1]).read_text())
raw = Path(sys.argv[2]).read_text().strip()
digest = sys.argv[3].removeprefix("sha256:")
try:
parsed = json.loads(raw)
documents = parsed if isinstance(parsed, list) else [parsed]
except json.JSONDecodeError:
documents = [
json.loads(line)
for line in raw.splitlines()
if line.strip()
]
for document in documents:
encoded = document.get("payload")
if not isinstance(encoded, str):
continue
statement = json.loads(base64.b64decode(encoded))
subjects = statement.get("subject", [])
predicate = statement.get("predicate")
if (
isinstance(predicate, dict)
and isinstance(predicate.get("Data"), str)
):
predicate = json.loads(predicate["Data"])
if (
any(
item.get("digest", {}).get("sha256") == digest
for item in subjects
if isinstance(item, dict)
)
and predicate == expected
):
break
else:
raise SystemExit(
"verified schema attestation does not bind this Release"
)
PY
oras pull "${schemaOciRef}" --output /work/schema
actual_bundle_digest="$(
sha256sum /work/schema/ljwx-health-schema-bundle.tar.gz \
| awk '{print "sha256:" $1}'
)"
test "${actual_bundle_digest}" = "${schemaBundleDigest}"
tar -xzf /work/schema/ljwx-health-schema-bundle.tar.gz \
-C /work/bundle
python3 - /work/bundle/bundle-metadata.json <<'PY'
import json
import hashlib
import os
import sys
from pathlib import Path
metadata = json.loads(Path(sys.argv[1]).read_text())
sbom_path = Path("/work/bundle/bundle-sbom.spdx.json")
provenance_path = Path(
"/work/bundle/bundle-provenance.intoto.json"
)
digest = lambda path: (
"sha256:" + hashlib.sha256(path.read_bytes()).hexdigest()
)
if (
digest(sbom_path) != metadata["sbomDigest"]
or digest(provenance_path)
!= metadata["provenanceDigest"]
):
raise SystemExit(
"schema bundle SBOM/provenance digest mismatch"
)
provenance = json.loads(provenance_path.read_text())
parameters = provenance["predicate"]["buildDefinition"][
"externalParameters"
]
production = next(
item for item in metadata["fingerprints"]
if item["profile"] == "production-pg18"
)
expected = {
"source": metadata["source"]["sha"],
"runner": metadata["runner"]["imageRef"],
"manifest": metadata["schemaManifestDigest"],
"media": metadata["mediaType"],
"canonicalProduction": production["digest"],
"adoptedProduction": metadata["adoption"][
"expectedPostFingerprint"
],
"adoptionPlan": metadata["adoption"]["planDigest"],
"adoptionEvidence": metadata["adoption"][
"catalogComparison"
]["evidenceDigest"],
"provenanceSource": parameters["sourceSha"],
"provenanceRunner": parameters["runnerImageRef"],
}
actual = {
"source": os.environ["sourceSha"],
"runner": os.environ["schemaRunnerImageRef"],
"manifest": os.environ["schemaManifestDigest"],
"media": os.environ["schemaBundleMediaType"],
"canonicalProduction": os.environ[
"canonicalProductionFingerprint"
],
"adoptedProduction": os.environ[
"expectedProductionFingerprint"
],
"adoptionPlan": os.environ["adoptionPlanDigest"],
"adoptionEvidence": os.environ[
"adoptionEvidenceDigest"
],
"provenanceSource": os.environ["sourceSha"],
"provenanceRunner": os.environ["schemaRunnerImageRef"],
}
if actual != expected:
raise SystemExit(
f"schema bundle metadata mismatch: {actual} != {expected}"
)
adoption = metadata["adoption"]
comparison = adoption["catalogComparison"]
plan_path = Path("/work/bundle") / os.environ[
"adoptionPlanPath"
]
evidence_path = Path("/work/bundle") / os.environ[
"adoptionEvidencePath"
]
plan = json.loads(plan_path.read_text())
plan_digest = "sha256:" + hashlib.sha256(
json.dumps(
plan,
ensure_ascii=True,
sort_keys=True,
separators=(",", ":"),
).encode()
).hexdigest()
if (
adoption["planPath"] != os.environ["adoptionPlanPath"]
or plan_digest != os.environ["adoptionPlanDigest"]
or comparison["evidencePath"]
!= os.environ["adoptionEvidencePath"]
or digest(evidence_path)
!= os.environ["adoptionEvidenceDigest"]
or adoption["expectedPreFingerprint"]
!= os.environ["adoptionExpectedPreFingerprint"]
or adoption["expectedPostFingerprint"]
!= os.environ["adoptionExpectedPostFingerprint"]
or plan["catalogComparison"] != comparison
):
raise SystemExit(
"schema bundle adoption contract mismatch"
)
PY
cd /work/bundle
export PGHOST="${DB_HOST}"
export PGPORT="${DB_PORT}"
export PGDATABASE="${DB_NAME}"
export LJWX_SCHEMA_SOURCE_SHA="${sourceSha}"
export LJWX_SCHEMA_ALLOW_PRODUCTION=true
DB_MIGRATOR_PASSWORD="$(cat /credentials/DB_PASSWORD)"
export DB_MIGRATOR_PASSWORD
adoption_plan=ljwx-boot/database/schema/adoption/production-k3s-eu-pg18.json
export PGUSER=ljwx_migrator
export PGPASSWORD="${DB_MIGRATOR_PASSWORD}"
verify_schema() {
ljwx-boot/scripts/schema-verify.sh \
--profile production-pg18 \
--target production \
--expected-database "${DB_NAME}" \
--database "${DB_NAME}" \
--output /work/verify-evidence.json
}
if verify_schema; then
echo "Production schema already satisfies the complete signed manifest; skipping retry upgrade"
else
ljwx-boot/scripts/schema-upgrade.sh \
--profile production-pg18 \
--target production \
--expected-database "${DB_NAME}" \
--database "${DB_NAME}" \
--adoption-plan "${adoption_plan}" \
--output /work/upgrade-evidence.json
verify_schema
fi
actual_fingerprint="$(
jq -r .logicalFingerprint /work/verify-evidence.json
)"
if [ "${actual_fingerprint}" != "${expectedProductionFingerprint}" ]; then
echo "Production schema fingerprint mismatch: expected=${expectedProductionFingerprint}, actual=${actual_fingerprint}" >&2
exit 1
fi
envFrom:
- configMapRef:
name: ljwx-health-config
- configMapRef:
name: ljwx-health-schema-release-unpromoted
volumeMounts:
- name: work
mountPath: /work
- name: registry
mountPath: /registry
readOnly: true
- name: migrator-credentials
mountPath: /credentials
readOnly: true
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
volumes:
- name: work
emptyDir: {}
- name: registry
secret:
secretName: ghcr-pull
items:
- key: .dockerconfigjson
path: config.json
- name: migrator-credentials
secret:
secretName: ljwx-health-schema-migrator
items:
- key: DB_PASSWORD
path: DB_PASSWORD
@@ -0,0 +1,5 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-health
resources:
- job-schema-bundle.yaml
@@ -0,0 +1,36 @@
# LJWX Health isolated schema restore drill
This package is deliberately dormant. No Argo CD application references it
until a reviewed activation commit pins all of the following:
- an exact signed LJWX Health Release receipt and schema ConfigMap;
- the schema-runner digest from that same Release;
- an exact pgBackRest backup label;
- a unique immutable Job name and either `pre-adoption` or `post-adoption`.
The cross-namespace read-only Role and RoleBinding live in
`apps/postgres-infra-operations/phase5-schema-restore-drill-rbac.yaml`. That
resource is synced by the PostgreSQL operations Argo application before the
restore Job is activated. Keeping it outside this
package prevents the parent LJWX Health namespace transformer from moving the
Role out of `infra`.
The Job restores only to a 12 GiB `emptyDir`. It never mounts a production PVC
or a production application Secret. Egress is limited to cluster DNS, the
Kubernetes API, the MinIO backup endpoint, and public HTTPS needed for
GHCR/Sigstore verification; production PostgreSQL port 5432 is not reachable.
The `pre-adoption` mode applies the signed adoption plan, upgrade, and complete
verify to a backup taken before production adoption. The `post-adoption` mode
re-runs the signed upgrade idempotently and verifies the already adopted
history. Both modes then start the exact `ljwx-boot@sha256` from the same
Release against the restored database and an ephemeral Redis process. The
application smoke uses generated credentials only and never reads the
production application Secret. Both modes report the exact backup and source
database identity, pgBackRest repository verification, RPO, end-to-end RTO,
critical row counts, catalog/RLS/extension inventory, TimescaleDB absence or
object counts, normalized fingerprint, and application readiness result.
Activation and cleanup are GitOps changes. Preserve Job logs and the
`RESTORE_DRILL_PASS` JSON before a later cleanup commit removes the Job and its
ephemeral volume.
@@ -0,0 +1,393 @@
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-health-schema-restore-drill-unconfigured
namespace: ljwx-health
labels:
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: schema-restore-drill
annotations:
argocd.argoproj.io/sync-wave: "4"
operations.ljwx.io/activation: dormant-requires-exact-release-and-backup
spec:
backoffLimit: 0
activeDeadlineSeconds: 3600
ttlSecondsAfterFinished: 604800
template:
metadata:
labels:
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: schema-restore-drill
spec:
restartPolicy: Never
serviceAccountName: ljwx-health-schema-restore-drill
automountServiceAccountToken: false
imagePullSecrets:
- name: ghcr-pull
securityContext:
fsGroup: 1001
fsGroupChangePolicy: OnRootMismatch
seccompProfile:
type: RuntimeDefault
initContainers:
- name: prepare-volumes
image: docker.io/bitnami/postgresql@sha256:9b62e6057d781853916a712ad2591a607de4529160e07c2eaf58d75ef6c45b9d
command:
- /bin/bash
- -ceu
- |
chmod 0700 /restore
chmod 0770 /socket /evidence
chown 1001:1001 /restore /socket /evidence
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
securityContext:
runAsNonRoot: false
runAsUser: 0
runAsGroup: 0
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["CHOWN"]
volumeMounts:
- name: restore
mountPath: /restore
- name: socket
mountPath: /socket
- name: evidence
mountPath: /evidence
- name: verify-release-and-fetch-backup-config
image: ghcr.io/lingjingwanxiang/ljwx-health/schema-runner:unpromoted
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- /scripts/prepare-release-and-backup.sh
envFrom:
- configMapRef:
name: ljwx-health-schema-release-unpromoted
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: work
mountPath: /work
- name: evidence
mountPath: /evidence
- name: backup-credentials
mountPath: /credentials
- name: pgbackrest-config
mountPath: /pgbackrest
- name: registry
mountPath: /registry
readOnly: true
- name: kube-api-access
mountPath: /var/run/secrets/kubernetes.io/serviceaccount
readOnly: true
containers:
- name: restore-postgres
image: docker.io/bitnami/postgresql@sha256:9b62e6057d781853916a712ad2591a607de4529160e07c2eaf58d75ef6c45b9d
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- /scripts/restore-postgres.sh
env:
- name: BACKUP_LABEL
value: BACKUP_LABEL_REQUIRED
resources:
requests:
cpu: 250m
memory: 512Mi
ephemeral-storage: 4Gi
limits:
cpu: "2"
memory: 2Gi
ephemeral-storage: 12Gi
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: restore
mountPath: /restore
- name: socket
mountPath: /socket
- name: evidence
mountPath: /evidence
- name: backup-credentials
mountPath: /credentials
readOnly: true
- name: pgbackrest-config
mountPath: /etc/pgbackrest
readOnly: true
- name: tmp
mountPath: /tmp
- name: verify-restored-schema
image: ghcr.io/lingjingwanxiang/ljwx-health/schema-runner:unpromoted
imagePullPolicy: IfNotPresent
command:
- /bin/bash
- /scripts/verify-restored-schema.sh
envFrom:
- configMapRef:
name: ljwx-health-schema-release-unpromoted
env:
- name: BACKUP_LABEL
value: BACKUP_LABEL_REQUIRED
- name: RESTORE_MODE
value: pre-adoption
resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: work
mountPath: /work
readOnly: true
- name: socket
mountPath: /socket
- name: evidence
mountPath: /evidence
- name: tmp
mountPath: /tmp
- name: ephemeral-redis
image: harbor.omniverseai.net/library/redis-stack-server@sha256:768c7c0782c0ffc931caf934c5491de6fe2a195b1f7be748ddd34746f50b3c22
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- /scripts/run-ephemeral-redis.sh
resources:
requests:
cpu: 25m
memory: 128Mi
limits:
cpu: 250m
memory: 512Mi
securityContext:
runAsNonRoot: true
runAsUser: 1001
runAsGroup: 1001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: evidence
mountPath: /evidence
- name: redis-tmp
mountPath: /tmp
- name: smoke-restored-application
image: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-boot:unpromoted
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- /scripts/smoke-restored-application.sh
env:
- name: LJWX_BOOT_IMAGE_REF
value: ghcr.io/lingjingwanxiang/ljwx-health/ljwx-boot:unpromoted
- name: SPRING_PROFILES_ACTIVE
value: k8s
- name: SERVER_PORT
value: "19998"
- name: DB_HOST
value: 127.0.0.1
- name: DB_PORT
value: "55432"
- name: DB_NAME
value: ljwx_health
- name: DB_USER
value: ljwx_app
- name: DB_PASSWORD
value: restore-drill-trust-only
- name: SPRING_DATASOURCE_URL
value: jdbc:postgresql://127.0.0.1:55432/ljwx_health?currentSchema=public&stringtype=unspecified
- name: SPRING_DATASOURCE_USERNAME
value: ljwx_app
- name: SPRING_DATASOURCE_PASSWORD
value: restore-drill-trust-only
- name: SPRING_DATASOURCE_DRIVER_CLASS_NAME
value: org.postgresql.Driver
- name: SPRING_FLYWAY_ENABLED
value: "false"
- name: REDIS_HOST
value: 127.0.0.1
- name: REDIS_PORT
value: "6379"
- name: REDIS_DB
value: "15"
- name: REDIS_PASSWORD
value: ""
- name: SPRING_REDIS_HOST
value: 127.0.0.1
- name: SPRING_REDIS_PORT
value: "6379"
- name: SPRING_REDIS_DATABASE
value: "15"
- name: SPRING_REDIS_PASSWORD
value: ""
- name: JAVA_TOOL_OPTIONS
value: ""
- name: JAVA_OPTS
value: -Xms128m -Xmx640m -Duser.timezone=Asia/Shanghai
- name: OTEL_TRACES_EXPORTER
value: none
- name: OTEL_METRICS_EXPORTER
value: none
- name: OTEL_LOGS_EXPORTER
value: none
- name: MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS
value: never
- name: MANAGEMENT_ENDPOINT_HEALTH_SHOW_COMPONENTS
value: never
- name: LOGGING_LEVEL_COM_LJWX_STARTER_SATOKEN_LOG
value: "OFF"
- name: OSS_ACCESS_KEY
value: restore-drill
- name: OSS_SECRET_KEY
value: restore-drill
- name: OSS_BUCKET_NAME
value: restore-drill
- name: OSS_ENDPOINT
value: http://127.0.0.1:1
- name: LJWX_LICENSE_ENABLED
value: "false"
resources:
requests:
cpu: 250m
memory: 768Mi
limits:
cpu: "2"
memory: 1536Mi
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: scripts
mountPath: /scripts
readOnly: true
- name: evidence
mountPath: /evidence
- name: app-logs
mountPath: /app/logs
- name: app-uploads
mountPath: /app/uploads
- name: app-tmp
mountPath: /tmp
volumes:
- name: scripts
configMap:
name: ljwx-health-schema-restore-drill-scripts
defaultMode: 0550
- name: restore
emptyDir:
sizeLimit: 12Gi
- name: socket
emptyDir:
medium: Memory
sizeLimit: 64Mi
- name: evidence
emptyDir:
medium: Memory
sizeLimit: 128Mi
- name: backup-credentials
emptyDir:
medium: Memory
sizeLimit: 1Mi
- name: pgbackrest-config
emptyDir:
medium: Memory
sizeLimit: 1Mi
- name: work
emptyDir:
sizeLimit: 512Mi
- name: registry
secret:
secretName: ghcr-pull
items:
- key: .dockerconfigjson
path: config.json
- name: tmp
emptyDir:
medium: Memory
sizeLimit: 512Mi
- name: redis-tmp
emptyDir:
medium: Memory
sizeLimit: 128Mi
- name: app-logs
emptyDir:
sizeLimit: 256Mi
- name: app-uploads
emptyDir:
sizeLimit: 64Mi
- name: app-tmp
emptyDir:
medium: Memory
sizeLimit: 256Mi
- name: kube-api-access
projected:
defaultMode: 0440
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
- configMap:
name: kube-root-ca.crt
items:
- key: ca.crt
path: ca.crt
- downwardAPI:
items:
- path: namespace
fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
@@ -0,0 +1,21 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- rbac.yaml
- networkpolicy.yaml
- job-schema-restore-drill.yaml
configMapGenerator:
- name: ljwx-health-schema-restore-drill-scripts
namespace: ljwx-health
files:
- scripts/prepare-release-and-backup.sh
- scripts/restore-postgres.sh
- scripts/verify-restored-schema.sh
- scripts/run-ephemeral-redis.sh
- scripts/smoke-restored-application.sh
generatorOptions:
labels:
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: schema-restore-drill
annotations:
argocd.argoproj.io/sync-wave: "-2"

Some files were not shown because too many files have changed in this diff Show More