@thanh01.pmt/curriculum-kit 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -14,35 +14,16 @@ var supabaseJs = require('@supabase/supabase-js');
14
14
  var url = require('url');
15
15
  var jsonrepair = require('jsonrepair');
16
16
  var rest = require('@octokit/rest');
17
- var workflowPkg = require('workflow');
17
+ var workflow = require('workflow');
18
18
 
19
19
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
20
20
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
21
21
 
22
- function _interopNamespace(e) {
23
- if (e && e.__esModule) return e;
24
- var n = Object.create(null);
25
- if (e) {
26
- Object.keys(e).forEach(function (k) {
27
- if (k !== 'default') {
28
- var d = Object.getOwnPropertyDescriptor(e, k);
29
- Object.defineProperty(n, k, d.get ? d : {
30
- enumerable: true,
31
- get: function () { return e[k]; }
32
- });
33
- }
34
- });
35
- }
36
- n.default = e;
37
- return Object.freeze(n);
38
- }
39
-
40
22
  var fs2__default = /*#__PURE__*/_interopDefault(fs2);
41
23
  var path3__default = /*#__PURE__*/_interopDefault(path3);
42
24
  var pLimit__default = /*#__PURE__*/_interopDefault(pLimit);
43
25
  var fsPromises__default = /*#__PURE__*/_interopDefault(fsPromises);
44
26
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
45
- var workflowPkg__namespace = /*#__PURE__*/_interopNamespace(workflowPkg);
46
27
 
47
28
  var __defProp = Object.defineProperty;
48
29
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -4261,9 +4242,9 @@ _Unit: {{UNIT_ID}} | Module: {{MODULE_ID}} | Format: Mermaid Diagrams + Printabl
4261
4242
  var SLIDE_LAYOUT_PRESETS = [
4262
4243
  {
4263
4244
  id: "title-hero",
4264
- name: "B\xECa & Gi\u1EDBi thi\u1EC7u (Title Cover)",
4245
+ name: "Title & Speaker Cover",
4265
4246
  category: "cover",
4266
- description: "Slide b\xECa m\u1EDF \u0111\u1EA7u \u1EA5n t\u01B0\u1EE3ng v\u1EDBi ti\xEAu \u0111\u1EC1 l\u1EDBn, ph\u1EE5 \u0111\u1EC1 v\xE0 th\xF4ng tin gi\u1EA3ng vi\xEAn.",
4247
+ description: "High-impact opening slide with large title, subtitle, and speaker credentials.",
4267
4248
  layout: {
4268
4249
  boxes: [
4269
4250
  {
@@ -4308,22 +4289,22 @@ var SLIDE_LAYOUT_PRESETS = [
4308
4289
  textAlign: center
4309
4290
  -->
4310
4291
 
4311
- # \u{1F680} Ti\xEAu \u0110\u1EC1 B\xE0i Gi\u1EA3ng Ch\xEDnh
4312
- ### Ph\u1EE5 \u0111\u1EC1 ng\u1EAFn g\u1ECDn, truy\u1EC1n c\u1EA3m h\u1EE9ng v\xE0 \u0111\u1ECBnh v\u1ECB m\u1EE5c ti\xEAu
4292
+ # \u{1F680} Engineering Scalable Cloud Systems
4293
+ ### Principles, Bottlenecks, and Real-World Architecture Patterns
4313
4294
 
4314
- **Gi\u1EA3ng vi\xEAn:** Th\u1EA7y Nguy\u1EC5n V\u0103n A | **Th\u1EDDi l\u01B0\u1EE3ng:** 45 ph\xFAt | **C\u1EA5p \u0111\u1ED9:** Trung c\u1EA5p
4295
+ **Presenter:** Senior Systems Architect | **Duration:** 45 mins | **Level:** Intermediate
4315
4296
 
4316
4297
  <!--
4317
4298
  Presenter Notes:
4318
- - Ch\xE0o m\u1EEBng h\u1ECDc vi\xEAn, gi\u1EDBi thi\u1EC7u m\u1EE5c ti\xEAu b\xE0i h\u1ECDc h\xF4m nay.
4319
- - Kh\u01A1i g\u1EE3i s\u1EF1 t\xF2 m\xF2 b\u1EB1ng c\xE2u h\u1ECFi m\u1EDF \u0111\u1EA7u v\u1EC1 b\xE0i to\xE1n th\u1EF1c t\u1EBF.
4299
+ - Welcome the audience and outline today's primary engineering goals.
4300
+ - Spark curiosity with an opening hook about scaling under high concurrency.
4320
4301
  -->`
4321
4302
  },
4322
4303
  {
4323
4304
  id: "two-column-split",
4324
- name: "So S\xE1nh 2 C\u1ED9t (2-Column Comparison)",
4305
+ name: "Two-Column Comparison",
4325
4306
  category: "split",
4326
- description: "B\u1ED1 c\u1EE5c chia \u0111\xF4i m\xE0n h\xECnh \u0111\u1EC3 so s\xE1nh ph\u01B0\u01A1ng ph\xE1p c\u0169 vs m\u1EDBi, l\xFD thuy\u1EBFt vs th\u1EF1c t\u1EBF.",
4307
+ description: "Balanced side-by-side comparison for legacy vs modern patterns or pros vs cons.",
4327
4308
  layout: {
4328
4309
  boxes: [
4329
4310
  {
@@ -4358,38 +4339,38 @@ Presenter Notes:
4358
4339
  pos: [670, 150, 530, 510]
4359
4340
  -->
4360
4341
 
4361
- ## \u2696\uFE0F So S\xE1nh Kh\xE1i Ni\u1EC7m & Gi\u1EA3i Ph\xE1p
4342
+ ## \u2696\uFE0F Monolithic Polling vs Event-Driven Architecture
4362
4343
 
4363
4344
  <div class="columns-2">
4364
4345
  <div>
4365
4346
 
4366
- ### \u{1F534} C\xE1ch Ti\u1EBFp C\u1EADn C\u0169
4367
- - X\u1EED l\xFD th\u1EE7 c\xF4ng, t\u1ED1n nhi\u1EC1u nh\xE2n l\u1EF1c
4368
- - D\u1EC5 ph\xE1t sinh sai s\u1ED1 do thao t\xE1c l\u1EB7p l\u1EA1i
4369
- - Kh\xF3 m\u1EDF r\u1ED9ng khi t\u1EA3i t\u0103ng \u0111\u1ED9t bi\u1EBFn
4347
+ ### \u{1F534} Legacy Polling
4348
+ - Heavy database CPU overhead on idle cycles
4349
+ - High network latency between updates
4350
+ - Prone to cascading timeouts during spikes
4370
4351
 
4371
4352
  </div>
4372
4353
  <div>
4373
4354
 
4374
- ### \u{1F7E2} Gi\u1EA3i Ph\xE1p T\u1ED1i \u01AFu M\u1EDBi
4375
- - T\u1EF1 \u0111\u1ED9ng h\xF3a ho\xE0n to\xE0n lu\u1ED3ng x\u1EED l\xFD
4376
- - Gi\xE1m s\xE1t tr\u1EA1ng th\xE1i th\u1EDDi gian th\u1EF1c
4377
- - Kh\u1EA3 n\u0103ng co gi\xE3n linh ho\u1EA1t theo nhu c\u1EA7u
4355
+ ### \u{1F7E2} Event-Driven Streams
4356
+ - Real-time event propagation via webhooks
4357
+ - Zero idle compute waste with serverless consumers
4358
+ - Automatic backpressure and queue isolation
4378
4359
 
4379
4360
  </div>
4380
4361
  </div>
4381
4362
 
4382
4363
  <!--
4383
4364
  Presenter Notes:
4384
- - Nh\u1EA5n m\u1EA1nh \u0111i\u1EC3m ngh\u1EBDn l\u1EDBn nh\u1EA5t \u1EDF c\u1ED9t b\xEAn tr\xE1i.
4385
- - D\u1EABn ch\u1EE9ng s\u1ED1 li\u1EC7u th\u1EF1c t\u1EBF ch\u1EE9ng minh t\xEDnh hi\u1EC7u qu\u1EA3 c\u1EE7a c\u1ED9t ph\u1EA3i.
4365
+ - Emphasize the core failure mode of periodic polling under sudden load spikes.
4366
+ - Ask the room if anyone has experienced database lockups caused by polling loops.
4386
4367
  -->`
4387
4368
  },
4388
4369
  {
4389
4370
  id: "code-explainer",
4390
- name: "Gi\u1EA3i Th\xEDch Code (Code Walkthrough)",
4371
+ name: "Code Walkthrough & Analysis",
4391
4372
  category: "code",
4392
- description: "C\u1ED9t tr\xE1i hi\u1EC3n th\u1ECB code snippet c\xF3 \u0111\xE1nh s\u1ED1, c\u1ED9t ph\u1EA3i gi\u1EA3i th\xEDch t\u1EEBng b\u01B0\u1EDBc ho\u1EA1t \u0111\u1ED9ng.",
4373
+ description: "Syntax-highlighted code block on the left with step-by-step key annotations on the right.",
4393
4374
  layout: {
4394
4375
  boxes: [
4395
4376
  {
@@ -4424,16 +4405,16 @@ Presenter Notes:
4424
4405
  pos: [740, 135, 460, 535]
4425
4406
  -->
4426
4407
 
4427
- ## \u{1F4BB} Ph\xE2n T\xEDch C\u1EA5u Tr\xFAc M\xE3 Ngu\u1ED3n
4408
+ ## \u{1F4BB} Robust Async Transaction Pipeline
4428
4409
 
4429
4410
  <div class="columns-2">
4430
4411
  <div>
4431
4412
 
4432
4413
  \`\`\`ts
4433
- // Kh\u1EDFi t\u1EA1o quy tr\xECnh ki\u1EC3m tra d\u1EEF li\u1EC7u
4414
+ // Process payment with idempotency guarantee
4434
4415
  export async function processPayment(order: Order) {
4435
4416
  const isValid = await validateOrder(order);
4436
- if (!isValid) throw new Error("Invalid");
4417
+ if (!isValid) throw new Error("ValidationFailed");
4437
4418
 
4438
4419
  const receipt = await chargeCard(order);
4439
4420
  return sendConfirmation(receipt);
@@ -4443,25 +4424,26 @@ export async function processPayment(order: Order) {
4443
4424
  </div>
4444
4425
  <div>
4445
4426
 
4446
- ### \u{1F50D} 3 \u0110i\u1EC3m C\u1EA7n Ch\xFA \xDD:
4447
- 1. **Ki\u1EC3m tra s\u1EDBm (Guard Clause):** Ki\u1EC3m tra t\xEDnh h\u1EE3p l\u1EC7 ngay d\xF2ng 3 \u0111\u1EC3 tr\xE1nh g\u1ECDi h\xE0m t\u1ED1n chi ph\xED.
4448
- 2. **B\u1EA5t \u0111\u1ED3ng b\u1ED9 (Async/Await):** \u0110\u1EA3m b\u1EA3o th\u1EE9 t\u1EF1 g\u1ECDi API t\xE0i ch\xEDnh chu\u1EA9n x\xE1c.
4449
- 3. **Ph\u1EA3n h\u1ED3i ng\u01B0\u1EDDi d\xF9ng:** Tr\u1EA3 v\u1EC1 bi\xEAn lai ho\xE0n ch\u1EC9nh sau khi giao d\u1ECBch th\xE0nh c\xF4ng.
4427
+ ### \u{1F50D} Key Engineering Invariants:
4428
+ 1. **Early Guard Clause:** Validates payload immediately at line 3 before triggering external calls.
4429
+ 2. **Deterministic Sequence:** Enforces strict order of operations using native \`await\`.
4430
+ 3. **Audit Trail:** Returns immutable payment receipt upon successful execution.
4450
4431
 
4451
4432
  </div>
4452
4433
  </div>
4453
4434
 
4454
4435
  <!--
4455
4436
  Presenter Notes:
4456
- - Y\xEAu c\u1EA7u h\u1ECDc vi\xEAn x\xE1c \u0111\u1ECBnh d\xF2ng code n\xE0o l\xE0 r\u1EE7i ro nh\u1EA5t n\u1EBFu k\u1EBFt n\u1ED1i m\u1EA1ng b\u1ECB r\u1EDBt.
4457
- - Nh\u1EAFc l\u1EA1i nguy\xEAn t\u1EAFc Fail-Fast trong l\u1EADp tr\xECnh ph\xF2ng th\u1EE7.
4437
+ - Walk through lines 1 to 7 sequentially.
4438
+ - Cold-call check: "What happens if line 6 throws a network timeout?"
4439
+ - Cognitive scaffolding: Highlight the importance of idempotency keys in payment APIs.
4458
4440
  -->`
4459
4441
  },
4460
4442
  {
4461
4443
  id: "metrics-3-card",
4462
- name: "L\u01B0\u1EDBi 3 Ch\u1EC9 S\u1ED1 (3-Metric Dashboard)",
4444
+ name: "3-Metric Key Results Grid",
4463
4445
  category: "metrics",
4464
- description: "Hi\u1EC3n th\u1ECB 3 ch\u1EC9 s\u1ED1 quan tr\u1ECDng (KPIs, Benchmarks, Impact) d\u1EA1ng card n\u1ED5i b\u1EADt.",
4446
+ description: "Three high-visibility KPI stat cards for benchmarks, impact, and success criteria.",
4465
4447
  layout: {
4466
4448
  boxes: [
4467
4449
  {
@@ -4506,43 +4488,43 @@ Presenter Notes:
4506
4488
  pos: [860, 165, 340, 485]
4507
4489
  -->
4508
4490
 
4509
- ## \u{1F4CA} 3 Ch\u1EC9 S\u1ED1 Th\xE0nh C\xF4ng C\u1EE7a D\u1EF1 \xC1n
4491
+ ## \u{1F4CA} Performance & Reliability Benchmarks
4510
4492
 
4511
4493
  <div class="columns-3">
4512
4494
  <div>
4513
4495
 
4514
- # \u26A1 99.9%
4515
- ### \u0110\u1ED9 S\u1EB5n S\xE0ng (Uptime)
4516
- H\u1EC7 th\u1ED1ng v\u1EADn h\xE0nh li\xEAn t\u1EE5c 24/7 v\u1EDBi kh\u1EA3 n\u0103ng t\u1EF1 ph\u1EE5c h\u1ED3i l\u1ED7i t\u1EE9c th\xEC.
4496
+ # \u26A1 99.99%
4497
+ ### SLA Availability
4498
+ High-availability multi-region cluster with automated failover routing.
4517
4499
 
4518
4500
  </div>
4519
4501
  <div>
4520
4502
 
4521
- # \u{1F680} 10x
4522
- ### T\u1ED1c \u0110\u1ED9 X\u1EED L\xFD
4523
- R\xFAt ng\u1EAFn th\u1EDDi gian bi\xEAn d\u1ECBch v\xE0 ph\u1EA3n h\u1ED3i ng\u01B0\u1EDDi d\xF9ng t\u1EEB gi\xE2y xu\u1ED1ng mili-gi\xE2y.
4503
+ # \u{1F680} 15ms
4504
+ ### P99 Latency
4505
+ Sub-20ms roundtrip response time via distributed edge caching.
4524
4506
 
4525
4507
  </div>
4526
4508
  <div>
4527
4509
 
4528
- # \u{1F6E1}\uFE0F 0
4529
- ### L\u1ED7 H\u1ED5ng B\u1EA3o M\u1EADt
4530
- \u0110\u1EA1t chu\u1EA9n \u0111\xE1nh gi\xE1 an to\xE0n OWASP v\xE0 m\xE3 h\xF3a d\u1EEF li\u1EC7u \u0111\u1EA7u cu\u1ED1i.
4510
+ # \u{1F6E1}\uFE0F Zero
4511
+ ### Unhandled Breaches
4512
+ Full SOC2 Type II compliance and end-to-end payload encryption.
4531
4513
 
4532
4514
  </div>
4533
4515
  </div>
4534
4516
 
4535
4517
  <!--
4536
4518
  Presenter Notes:
4537
- - Tr\xECnh b\xE0y l\u1EA7n l\u01B0\u1EE3t 3 m\u1EE5c ti\xEAu \u0111o l\u01B0\u1EDDng \u0111\u01B0\u1EE3c c\u1EE7a h\u1EC7 th\u1ED1ng.
4538
- - Nh\u1EA5n m\u1EA1nh ch\u1EC9 s\u1ED1 th\u1EE9 2 l\xE0 tr\u1ECDng t\xE2m k\u1EF9 thu\u1EADt c\u1EE7a b\xE0i h\u1ECDc h\xF4m nay.
4519
+ - Highlight the 15ms P99 latency target as the primary technical milestone.
4520
+ - Connect these metrics directly to the architecture decisions discussed next.
4539
4521
  -->`
4540
4522
  },
4541
4523
  {
4542
4524
  id: "process-flow",
4543
- name: "S\u01A1 \u0110\u1ED3 Quy Tr\xECnh (Process / Flowchart)",
4525
+ name: "Architecture & Process Flow",
4544
4526
  category: "diagram",
4545
- description: "Quy tr\xECnh nhi\u1EC1u b\u01B0\u1EDBc v\u1EDBi s\u01A1 \u0111\u1ED3 Mermaid tr\u1EF1c quan k\xE8m ghi ch\xFA t\xF3m t\u1EAFt.",
4527
+ description: "Multi-stage workflow with responsive Mermaid diagram and phase summaries.",
4546
4528
  layout: {
4547
4529
  boxes: [
4548
4530
  {
@@ -4577,33 +4559,33 @@ Presenter Notes:
4577
4559
  pos: [80, 465, 1120, 205]
4578
4560
  -->
4579
4561
 
4580
- ## \u{1F504} Ki\u1EBFn Tr\xFAc Lu\u1ED3ng D\u1EEF Li\u1EC7u 4 B\u01B0\u1EDBc
4562
+ ## \u{1F504} End-to-End Data Ingestion Pipeline
4581
4563
 
4582
4564
  \`\`\`mermaid
4583
4565
  graph LR
4584
- A[1. Client Request] --> B[2. API Gateway]
4585
- B --> C[3. Auth Service]
4586
- C --> D[4. Database Cluster]
4566
+ A[1. Client Ingress] --> B[2. API Gateway]
4567
+ B --> C[3. Auth Verifier]
4568
+ C --> D[4. Distributed DB]
4587
4569
  style A fill:#0284c7,stroke:#38bdf8,stroke-width:2px,color:#fff
4588
4570
  style B fill:#1e293b,stroke:#64748b,stroke-width:2px,color:#fff
4589
4571
  style C fill:#059669,stroke:#34d399,stroke-width:2px,color:#fff
4590
4572
  style D fill:#7c3aed,stroke:#a78bfa,stroke-width:2px,color:#fff
4591
4573
  \`\`\`
4592
4574
 
4593
- - **Giai \u0111o\u1EA1n 1 & 2:** Ti\u1EBFp nh\u1EADn y\xEAu c\u1EA7u t\u1EEB client, \u0111i\u1EC1u h\u01B0\u1EDBng t\u1EA3i v\xE0 gi\u1EDBi h\u1EA1n t\u1EA7n su\u1EA5t (Rate Limiting).
4594
- - **Giai \u0111o\u1EA1n 3 & 4:** X\xE1c th\u1EF1c token JWT ph\xE2n quy\u1EC1n tr\u01B0\u1EDBc khi truy v\u1EA5n d\u1EEF li\u1EC7u t\u1EEB DB Cluster.
4575
+ - **Ingress & Gateway:** Rate-limits traffic and applies reverse-proxy load balancing.
4576
+ - **Verification & Storage:** Decodes cryptographic JWT tokens before transactional write commit.
4595
4577
 
4596
4578
  <!--
4597
4579
  Presenter Notes:
4598
- - Tr\u1ECF tr\u1EF1c ti\u1EBFp v\xE0o t\u1EEBng node c\u1EE7a s\u01A1 \u0111\u1ED3 Mermaid khi thuy\u1EBFt tr\xECnh.
4599
- - \u0110\u1EB7t c\xE2u h\u1ECFi: "N\u1EBFu b\u01B0\u1EDBc 3 th\u1EA5t b\u1EA1i th\xEC b\u01B0\u1EDBc 4 c\xF3 \u0111\u01B0\u1EE3c g\u1ECDi kh\xF4ng?"
4580
+ - Trace each hop across the diagram from left to right.
4581
+ - Ask: "Where should rate limiting be applied to prevent DDoS attacks?"
4600
4582
  -->`
4601
4583
  },
4602
4584
  {
4603
4585
  id: "quote-highlight",
4604
- name: "Th\xF4ng \u0110i\u1EC7p Then Ch\u1ED1t (Quote / Highlight)",
4586
+ name: "Core Principle & Quote Highlight",
4605
4587
  category: "quote",
4606
- description: "Tr\xEDch d\u1EABn danh ng\xF4n ho\u1EB7c nguy\xEAn t\u1EAFc v\xE0ng c\u1EA7n ghi nh\u1EDB s\xE2u s\u1EAFc.",
4588
+ description: "Prominent quote callout for fundamental engineering rules or memorable mantras.",
4607
4589
  layout: {
4608
4590
  boxes: [
4609
4591
  {
@@ -4634,23 +4616,23 @@ Presenter Notes:
4634
4616
  fontSize: 1.4rem
4635
4617
  -->
4636
4618
 
4637
- ## \u{1F4A1} Nguy\xEAn T\u1EAFc Thi\u1EBFt K\u1EBF C\u1ED1t L\xF5i
4619
+ ## \u{1F4A1} Foundational Architecture Principle
4638
4620
 
4639
- > "S\u1EF1 \u0111\u01A1n gi\u1EA3n l\xE0 \u0111i\u1EC1u ki\u1EC7n ti\xEAn quy\u1EBFt cho \u0111\u1ED9 tin c\u1EADy. M\u1ED9t ki\u1EBFn tr\xFAc t\u1ED1t l\xE0 khi b\u1EA5t k\u1EF3 l\u1EADp tr\xECnh vi\xEAn m\u1EDBi n\xE0o c\u0169ng c\xF3 th\u1EC3 \u0111\u1ECDc hi\u1EC3u lu\u1ED3ng ho\u1EA1t \u0111\u1ED9ng ch\u1EC9 sau 15 ph\xFAt."
4621
+ > "Simplicity is prerequisite for reliability. A well-engineered distributed system is one where a new engineer can understand the request flow within their first hour."
4640
4622
  >
4641
4623
  > \u2014 **Edsger W. Dijkstra**
4642
4624
 
4643
4625
  <!--
4644
4626
  Presenter Notes:
4645
- - D\u1EEBng l\u1EA1i 5 gi\xE2y im l\u1EB7ng \u0111\u1EC3 c\u1EA3 l\u1EDBp c\xF9ng suy ng\u1EABm v\u1EC1 th\xF4ng \u0111i\u1EC7p.
4646
- - Y\xEAu c\u1EA7u m\u1ED9t h\u1ECDc vi\xEAn chia s\u1EBB suy ngh\u0129 v\u1EC1 c\xE1ch \xE1p d\u1EE5ng nguy\xEAn t\u1EAFc n\xE0y v\xE0o b\xE0i t\u1EADp l\u1EDBn.
4627
+ - Pause for 5 seconds of silence to let the quote sink in.
4628
+ - Ask: "How do our current architectural choices reflect this principle?"
4647
4629
  -->`
4648
4630
  },
4649
4631
  {
4650
4632
  id: "quiz-checkpoint",
4651
- name: "Tr\u1EAFc Nghi\u1EC7m T\u01B0\u01A1ng T\xE1c (Quiz Checkpoint)",
4633
+ name: "Interactive Formative Checkpoint",
4652
4634
  category: "assessment",
4653
- description: "C\xE2u h\u1ECFi t\u01B0\u01A1ng t\xE1c Formative Checkpoint k\xE8m c\xE1c l\u1EF1a ch\u1ECDn A/B/C/D \u0111\u1EC3 l\u1EDBp b\xECnh ch\u1ECDn.",
4635
+ description: "Interactive multiple-choice diagnostic checkpoint with A/B/C/D voting options.",
4654
4636
  layout: {
4655
4637
  boxes: [
4656
4638
  {
@@ -4689,27 +4671,27 @@ Presenter Notes:
4689
4671
  fontSize: 1.15rem
4690
4672
  -->
4691
4673
 
4692
- ## \u{1F3AF} Ki\u1EC3m Tra Nhanh: B\u1EA1n \u0110\xE3 Hi\u1EC3u \u0110\xFAng?
4674
+ ## \u{1F3AF} Formative Checkpoint: Verify Your Understanding
4693
4675
 
4694
- > **C\xE2u h\u1ECFi:** Trong ki\u1EBFn tr\xFAc Microservices, c\u01A1 ch\u1EBF n\xE0o gi\xFAp \u0111\u1EA3m b\u1EA3o tin nh\u1EAFn kh\xF4ng b\u1ECB th\u1EA5t l\u1EA1c khi d\u1ECBch v\u1EE5 nh\u1EADn g\u1EB7p s\u1EF1 c\u1ED1 t\u1EA1m th\u1EDDi?
4676
+ > **Question:** In an event-driven architecture, which pattern guarantees that messages are never permanently lost if a downstream consumer crashes?
4695
4677
 
4696
- - **A.** G\u1ECDi API HTTP tr\u1EF1c ti\u1EBFp v\u1EDBi timeout ng\u1EAFn
4697
- - **B.** S\u1EED d\u1EE5ng Message Queue (H\xE0ng \u0111\u1EE3i tin nh\u1EAFn) c\xF3 c\u01A1 ch\u1EBF Retry & DLQ
4698
- - **C.** L\u01B0u t\u1EA1m v\xE0o b\u1ED9 nh\u1EDB RAM c\u1EE7a m\xE1y kh\xE1ch (Client In-Memory Storage)
4699
- - **D.** B\u1ECF qua tin nh\u1EAFn b\u1ECB l\u1ED7i v\xE0 th\xF4ng b\xE1o ng\u01B0\u1EDDi d\xF9ng g\u1EEDi l\u1EA1i t\u1EEB \u0111\u1EA7u
4678
+ - **A.** Direct synchronous HTTP request with short 2-second timeout
4679
+ - **B.** Persistent Message Broker with Dead-Letter Queue (DLQ) & retry policy
4680
+ - **C.** In-memory client RAM cache without persistence
4681
+ - **D.** Discarding failed packets and prompting the user to re-submit
4700
4682
 
4701
4683
  <!--
4702
4684
  Presenter Notes:
4703
- - D\xE0nh 30 gi\xE2y cho h\u1ECDc vi\xEAn gi\u01A1 tay ho\u1EB7c b\xECnh ch\u1ECDn \u0111\xE1p \xE1n.
4704
- - \u0110\xE1p \xE1n ch\xEDnh x\xE1c: B.
4705
- - Gi\u1EA3i th\xEDch v\xEC sao \u0111\xE1p \xE1n A v\xE0 C ti\u1EC1m \u1EA9n nguy c\u01A1 m\u1EA5t m\xE1t d\u1EEF li\u1EC7u nghi\xEAm tr\u1ECDng.
4685
+ - Give students 30 seconds to vote on option A, B, C, or D.
4686
+ - Correct answer: B.
4687
+ - Explain why options A and C introduce catastrophic data loss in production.
4706
4688
  -->`
4707
4689
  },
4708
4690
  {
4709
4691
  id: "tiered-practice-3cards",
4710
- name: "Th\u1EF1c H\xE0nh Ph\xE2n H\xF3a (Tiered Practice)",
4692
+ name: "Tiered Differentiation (Bronze / Silver / Gold)",
4711
4693
  category: "content",
4712
- description: "3 c\u1EA5p b\u1EADc nhi\u1EC7m v\u1EE5 \u0110\u1ED3ng - B\u1EA1c - V\xE0ng \u0111\u1EC3 h\u1ECDc sinh t\u1EF1 ch\u1ECDn m\u1EE9c th\u1EED th\xE1ch ph\xF9 h\u1EE3p.",
4694
+ description: "Three progressive competency tiers allowing self-paced learning acceleration.",
4713
4695
  layout: {
4714
4696
  boxes: [
4715
4697
  {
@@ -4752,46 +4734,46 @@ Presenter Notes:
4752
4734
  pos: [850, 140, 350, 520]
4753
4735
  -->
4754
4736
 
4755
- ## \u{1F6E0}\uFE0F Th\u1EED Th\xE1ch Ph\xE2n H\xF3a 3 C\u1EA5p \u0110\u1ED9 (Tiered Lab)
4737
+ ## \u{1F6E0}\uFE0F Hands-On Challenge: 3-Tier Differentiation
4756
4738
 
4757
4739
  <div class="columns-3">
4758
4740
  <div>
4759
4741
 
4760
- ### \u{1F949} H\u1EA1ng \u0110\u1ED3ng (Bronze)
4761
- - Vi\u1EBFt h\xE0m x\u1EED l\xFD c\u01A1 b\u1EA3n
4762
- - V\u01B0\u1EE3t qua 3 test case m\u1EABu
4763
- - *M\u1EE5c ti\xEAu: \u0110\u1EA1t chu\u1EA9n ki\u1EBFn th\u1EE9c c\u01A1 b\u1EA3n*
4742
+ ### \u{1F949} Bronze Tier
4743
+ - Implement baseline function
4744
+ - Pass 3 baseline unit tests
4745
+ - *Goal: Foundational mastery*
4764
4746
 
4765
4747
  </div>
4766
4748
  <div>
4767
4749
 
4768
- ### \u{1F948} H\u1EA1ng B\u1EA1c (Silver)
4769
- - Th\xEAm ki\u1EC3m tra \u0111i\u1EC1u ki\u1EC7n bi\xEAn
4770
- - T\u1ED1i \u01B0u \u0111\u1ED9 ph\u1EE9c t\u1EA1p thu\u1EADt to\xE1n O(N)
4771
- - *M\u1EE5c ti\xEAu: V\u1EEFng v\xE0ng k\u1EF9 n\u0103ng th\u1EF1c chi\u1EBFn*
4750
+ ### \u{1F948} Silver Tier
4751
+ - Add boundary error handling
4752
+ - Enforce O(N) memory complexity
4753
+ - *Goal: Production-ready code*
4772
4754
 
4773
4755
  </div>
4774
4756
  <div>
4775
4757
 
4776
- ### \u{1F947} H\u1EA1ng V\xE0ng (Gold)
4777
- - Thi\u1EBFt k\u1EBF t\xEDnh n\u0103ng m\u1EDF r\u1ED9ng
4778
- - Vi\u1EBFt Unit Test t\u1EF1 \u0111\u1ED9ng bao ph\u1EE7 90%
4779
- - *M\u1EE5c ti\xEAu: S\xE1ng t\u1EA1o & b\u1EE9t ph\xE1*
4758
+ ### \u{1F947} Gold Tier
4759
+ - Design distributed retry queue
4760
+ - Add 95% automated test coverage
4761
+ - *Goal: Architectural leadership*
4780
4762
 
4781
4763
  </div>
4782
4764
  </div>
4783
4765
 
4784
4766
  <!--
4785
4767
  Presenter Notes:
4786
- - To\xE0n b\u1ED9 l\u1EDBp \u0111\u1EC1u b\u1EAFt \u0111\u1EA7u t\u1EEB H\u1EA1ng \u0110\u1ED3ng \u0111\u1EC3 \u0111\u1EA3m b\u1EA3o hi\u1EC3u b\xE0i.
4787
- - Khuy\u1EBFn kh\xEDch h\u1ECDc vi\xEAn \u0111\xE3 xong Bronze ch\u1EE7 \u0111\u1ED9ng th\u1EED s\u1EE9c v\u1EDBi Silver v\xE0 Gold.
4768
+ - All learners begin at Bronze to establish baseline competency.
4769
+ - Accelerate self-directed learners to Silver and Gold as they complete each tier.
4788
4770
  -->`
4789
4771
  },
4790
4772
  {
4791
4773
  id: "agenda-timeline",
4792
- name: "L\u1ED9 Tr\xECnh Bu\u1ED5i H\u1ECDc (Agenda Table)",
4774
+ name: "Session Agenda & Milestones",
4793
4775
  category: "content",
4794
- description: "B\u1EA3ng l\u1ED9 tr\xECnh th\u1EDDi gian chi ti\u1EBFt theo t\u1EEBng giai \u0111o\u1EA1n v\xE0 s\u1EA3n ph\u1EA9m \u0111\u1EA7u ra.",
4776
+ description: "Structured time-budgeted agenda mapping stages to measurable deliverables.",
4795
4777
  layout: {
4796
4778
  boxes: [
4797
4779
  {
@@ -4818,25 +4800,25 @@ Presenter Notes:
4818
4800
  pos: [80, 140, 1120, 520]
4819
4801
  -->
4820
4802
 
4821
- ## \u{1F5FA}\uFE0F L\u1ED9 Tr\xECnh Ho\u1EA1t \u0110\u1ED9ng & M\u1EE5c Ti\xEAu
4803
+ ## \u{1F5FA}\uFE0F Milestone Roadmap & Time Budget
4822
4804
 
4823
- | Th\u1EDDi l\u01B0\u1EE3ng | Giai \u0111o\u1EA1n | Tr\u1ECDng t\xE2m ho\u1EA1t \u0111\u1ED9ng | K\u1EBFt qu\u1EA3 \u0111\u1EA1t \u0111\u01B0\u1EE3c |
4805
+ | Allocated Time | Session Stage | Core Learning Focus | Deliverable Milestone |
4824
4806
  |:---:|---|---|---|
4825
- | **05'** | Kh\u1EDFi \u0111\u1ED9ng | T\xECnh hu\u1ED1ng th\u1EF1c t\u1EBF & Th\u1EED th\xE1ch | Nh\u1EADn di\u1EC7n b\xE0i to\xE1n c\u1EA7n gi\u1EA3i |
4826
- | **15'** | Kh\xE1m ph\xE1 | Ph\xE2n t\xEDch 2 kh\xE1i ni\u1EC7m c\u1ED1t l\xF5i | Hi\u1EC3u b\u1EA3n ch\u1EA5t thu\u1EADt to\xE1n |
4827
- | **20'** | Th\u1EF1c h\xE0nh | Code Lab t\u1EA1i m\xE1y theo 3 b\u1EADc | Ho\xE0n th\xE0nh t\u1ED1i thi\u1EC3u H\u1EA1ng \u0110\u1ED3ng |
4828
- | **05'** | T\u1ED5ng k\u1EBFt | Tr\u1EAFc nghi\u1EC7m ph\u1EA3n x\u1EA1 & \u0110\xE1nh gi\xE1 | H\u1EC7 th\u1ED1ng h\xF3a to\xE0n b\u1ED9 ki\u1EBFn th\u1EE9c |
4807
+ | **05 mins** | Warm-Up | Real-World Scenario & Context | Identify system bottleneck |
4808
+ | **15 mins** | Concept Discovery | Core Architectural Patterns | Deconstruct message queue models |
4809
+ | **20 mins** | Hands-On Lab | Tiered Code Implementation | Complete Bronze + Silver tasks |
4810
+ | **05 mins** | Wrap-Up | Formative Checkpoint & Debrief | Consolidate key principles |
4829
4811
 
4830
4812
  <!--
4831
4813
  Presenter Notes:
4832
- - Gi\u1EDBi thi\u1EC7u t\u1ED5ng quan \u0111\u1EC3 h\u1ECDc vi\xEAn bi\u1EBFt r\xF5 c\u1EA5u tr\xFAc bu\u1ED5i h\u1ECDc v\xE0 ch\u1EE7 \u0111\u1ED9ng ph\xE2n b\u1ED5 th\u1EDDi gian.
4814
+ - Walk through the time budget so students understand expectations and pacing.
4833
4815
  -->`
4834
4816
  },
4835
4817
  {
4836
4818
  id: "takeaways-summary",
4837
- name: "T\u1ED5ng K\u1EBFt C\u1ED1t L\xF5i (Key Takeaways)",
4819
+ name: "Key Takeaways & Wrap-up",
4838
4820
  category: "content",
4839
- description: "Slide k\u1EBFt lu\u1EADn v\u1EDBi 3 b\xE0i h\u1ECDc \u0111\u1EAFt gi\xE1 nh\u1EA5t v\xE0 h\xE0nh \u0111\u1ED9ng ti\u1EBFp theo.",
4821
+ description: "Three memorable synthesis takeaways with actionable next steps.",
4840
4822
  layout: {
4841
4823
  boxes: [
4842
4824
  {
@@ -4875,18 +4857,18 @@ Presenter Notes:
4875
4857
  fontSize: 1.1rem
4876
4858
  -->
4877
4859
 
4878
- ## \u{1F3C1} 3 \u0110i\u1EC1u C\u1EA7n Nh\u1EDB Sau Bu\u1ED5i H\u1ECDc
4860
+ ## \u{1F3C1} Key Takeaways & Action Items
4879
4861
 
4880
- 1. **Hi\u1EC3u b\u1EA3n ch\u1EA5t tr\u01B0\u1EDBc khi vi\u1EBFt m\xE3:** Thu\u1EADt to\xE1n \u0111\xFAng lu\xF4n quan tr\u1ECDng h\u01A1n c\xFA ph\xE1p b\xF3ng b\u1EA9y.
4881
- 2. **Ki\u1EC3m so\xE1t \u0111i\u1EC1u ki\u1EC7n bi\xEAn:** 80% l\u1ED7i h\u1EC7 th\u1ED1ng ph\xE1t sinh t\u1EEB c\xE1c d\u1EEF li\u1EC7u \u0111\u1EA7u v\xE0o kh\xF4ng l\u01B0\u1EDDng tr\u01B0\u1EDBc.
4882
- 3. **Thi\u1EBFt k\u1EBF h\u01B0\u1EDBng m\xF4-\u0111un:** Chia nh\u1ECF v\u1EA5n \u0111\u1EC1 l\u1EDBn th\xE0nh c\xE1c h\xE0m \u0111\u1ED9c l\u1EADp d\u1EC5 ki\u1EC3m th\u1EED.
4862
+ 1. **Decouple Before Scaling:** Separate producer rate from consumer processing capacity.
4863
+ 2. **Design for Failure:** Always configure retries, backoff intervals, and dead-letter queues.
4864
+ 3. **Measure End-to-End:** Track P99 latency and error rates across all distributed boundaries.
4883
4865
 
4884
- > \u{1F4DD} **Nhi\u1EC7m v\u1EE5 v\u1EC1 nh\xE0:** Ho\xE0n thi\u1EC7n b\xE0i th\u1EF1c h\xE0nh H\u1EA1ng B\u1EA1c v\xE0 chu\u1EA9n b\u1ECB c\xE2u h\u1ECFi cho bu\u1ED5i Review tu\u1EA7n t\u1EDBi.
4866
+ > \u{1F4DD} **Next Action Item:** Complete the Silver Tier refactoring challenge before our next peer-review session.
4885
4867
 
4886
4868
  <!--
4887
4869
  Presenter Notes:
4888
- - T\xF3m t\u1EAFt s\xFAc t\xEDch trong 2 ph\xFAt cu\u1ED1i bu\u1ED5i.
4889
- - Tuy\xEAn d\u01B0\u01A1ng c\xE1c b\u1EA1n c\xF3 gi\u1EA3i ph\xE1p xu\u1EA5t s\u1EAFc trong ph\u1EA7n th\u1EF1c h\xE0nh.
4870
+ - Deliver a crisp 2-minute wrap-up reinforcing the 3 core takeaways.
4871
+ - Acknowledge strong peer collaboration during the hands-on lab.
4890
4872
  -->`
4891
4873
  }
4892
4874
  ];
@@ -9575,14 +9557,14 @@ init_errors();
9575
9557
 
9576
9558
  // src/workflow/gateSettings.ts
9577
9559
  var DEFAULT_GATE_SETTINGS = Object.freeze({
9578
- LEARNER_PROFILE: "HITL",
9579
- PROJECT_BRIEF: "HITL",
9580
- REFERENCE_PACK: "HITL",
9581
- CURRICULUM_FRAMEWORK: "HITL",
9582
- KNOWLEDGE_EXPOSITION: "LLM_JUDGE",
9583
- STYLE_GUIDE: "HITL",
9584
- ART_DIRECTION: "HITL",
9585
- LESSON: "LLM_JUDGE",
9560
+ LEARNER_PROFILE: "OFF",
9561
+ PROJECT_BRIEF: "OFF",
9562
+ REFERENCE_PACK: "OFF",
9563
+ CURRICULUM_FRAMEWORK: "OFF",
9564
+ KNOWLEDGE_EXPOSITION: "OFF",
9565
+ STYLE_GUIDE: "OFF",
9566
+ ART_DIRECTION: "OFF",
9567
+ LESSON: "OFF",
9586
9568
  ACT: "OFF",
9587
9569
  QUIZ: "OFF",
9588
9570
  SLIDE: "OFF",
@@ -9590,7 +9572,7 @@ var DEFAULT_GATE_SETTINGS = Object.freeze({
9590
9572
  WKS: "OFF",
9591
9573
  HANDOUT: "OFF",
9592
9574
  EXT: "OFF",
9593
- CODE: "LLM_JUDGE",
9575
+ CODE: "OFF",
9594
9576
  MEDIA_SCRIPT: "OFF",
9595
9577
  EXIT_TICKET: "OFF"
9596
9578
  });
@@ -11479,9 +11461,10 @@ Fix ALL issues above and output the complete corrected document.` }],
11479
11461
  const taskState = pipelineState.tasks[lessonCode] || {};
11480
11462
  const currentLessonInfo = taskState.LESSON;
11481
11463
  const lessonGate = gateModeFor(gates, "LESSON");
11482
- let isLessonApproved = lessonGate !== "HITL" || // OFF/LLM_JUDGE: không cần duyệt người
11483
- currentLessonInfo?.state === "approved" || currentLessonInfo?.review?.decision === "APPROVED" || approvedArtifacts.includes("LESSON") || approvedArtifacts.includes(lessonCode);
11484
- if (lessonGate === "LLM_JUDGE" && !isLessonApproved && lessonContent) {
11464
+ const humanApproved = currentLessonInfo?.state === "approved" || currentLessonInfo?.review?.decision === "APPROVED" || approvedArtifacts.includes("LESSON") || approvedArtifacts.includes(lessonCode);
11465
+ const needsJudge = lessonGate === "LLM_JUDGE" && !humanApproved;
11466
+ let isLessonApproved = lessonGate === "OFF" || humanApproved;
11467
+ if (needsJudge && lessonContent) {
11485
11468
  onProgress?.("@reviewer", `\u{1F916} Executing LLM-as-Judge academic evaluation on LESSON_${lessonCode}.md...`);
11486
11469
  try {
11487
11470
  let injectYamlReviewMetadata2 = function(content, meta) {
@@ -12520,6 +12503,8 @@ async function generateSingleArtifact(req) {
12520
12503
  extraContext,
12521
12504
  config,
12522
12505
  contextSot = {},
12506
+ mediaPolicy,
12507
+ researchContext,
12523
12508
  options = {},
12524
12509
  onChunk
12525
12510
  } = req;
@@ -12557,7 +12542,10 @@ ${langRule}
12557
12542
  3. PEDAGOGICAL GROUNDING: Strictly align with the ${pedagogy.toUpperCase()} model, Bloom's Revised Taxonomy, and IEEE/ACM CS2023 guidelines.
12558
12543
  4. SCAFFOLDING LEVEL: "${scaffoldingLevel.toUpperCase()}".${scaffoldingRule}
12559
12544
  5. TARGET AUDIENCE: ${gradeLevel ? `Grade ${gradeLevel}, ` : ""}${targetAge} on ${hwStr}.${deviceRule}${classDynamicsRule}${extraContextRule}
12560
- 6. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}`;
12545
+ 6. OUTPUT INTEGRITY: Output clean Markdown starting directly with YAML frontmatter. Do not wrap entire response in triple backticks.${artifactSpecializedPrompt}${mediaPolicy ? `
12546
+
12547
+ IMAGE PLANNING (media ledger) \u2014 QUOTA GEN: t\u1ED1i \u0111a ${mediaPolicy.maxGenImagesPerArtifact} \u1EA3nh AI cho artifact n\xE0y (\u1EA3nh search kho kh\xF4ng gi\u1EDBi h\u1EA1n).
12548
+ Khi n\u1ED9i dung c\u1EA7n minh h\u1ECDa (diagram quy tr\xECnh, s\u01A1 \u0111\u1ED3 kh\xE1i ni\u1EC7m, step-by-step visual), ch\xE8n placeholder \u0111\xFAng format [IMAGE: slug] (slug ch\u1EEF th\u01B0\u1EDDng-g\u1EA1ch ngang, unique trong b\xE0i, vd img-for-loop-diagram) T\u1EA0I \u0110\xDANG CH\u1ED6 c\u1EA7n \u1EA3nh, tr\xEAn d\xF2ng ri\xEAng. KH\xD4NG t\u1EF1 sinh \u1EA3nh, KH\xD4NG d\xF9ng markdown image \u2014 placeholder s\u1EBD \u0111\u01B0\u1EE3c thay b\u1EB1ng \u1EA3nh th\u1EADt sau khi Media Curator duy\u1EC7t. Ch\u1EC9 \u0111\u1EB7t cho \u1EA3nh TH\u1EF0C S\u1EF0 c\u1EA7n thi\u1EBFt.` : ""}`;
12561
12549
  const userPrompt = `### CONTEXT GROUND TRUTH:
12562
12550
  - Topic / Concept Domain: "${topic}"
12563
12551
  - Lesson ID: "${lessonId || "ON_DEMAND"}"
@@ -12572,6 +12560,9 @@ ${selectedDevices ? `- Device Mode: "${selectedDevices}"
12572
12560
  ` : ""}${extraContext ? `- User Specific Guidance & Requirements: "${extraContext}"
12573
12561
  ` : ""}${config ? `- Full Task Config: ${JSON.stringify(config)}
12574
12562
  ` : ""}
12563
+ ${researchContext ? `### RESEARCH GROUND TRUTH (fact-checked context \u2014 \u01B0u ti\xEAn d\xF9ng, KH\xD4NG b\u1ECBa s\u1ED1 li\u1EC7u):
12564
+ ${researchContext.slice(0, 4e3)}
12565
+ ` : ""}
12575
12566
  ${contextSot.framework ? `### SOT CURRICULUM FRAMEWORK:
12576
12567
  ${contextSot.framework.slice(0, 1500)}
12577
12568
  ` : ""}
@@ -12612,13 +12603,171 @@ Generate the complete, thorough, publication-grade "${artifactType.toUpperCase()
12612
12603
  }
12613
12604
  );
12614
12605
  const { thought, content } = extractThoughtAndContent(rawResult);
12615
- return {
12616
- artifactType,
12617
- filename: meta.filename,
12618
- content: content || accumulatedContent,
12619
- persona: meta.persona,
12620
- thought: thought || accumulatedThought
12606
+ const finalContent = content || accumulatedContent;
12607
+ const finalThought = thought || accumulatedThought;
12608
+ const gates = resolveGateSettings(req.gateSettings);
12609
+ const kitGateType = {
12610
+ lesson: "LESSON",
12611
+ slide: "SLIDE",
12612
+ act: "ACT",
12613
+ quiz: "QUIZ",
12614
+ guide: "GUIDE",
12615
+ handout: "HANDOUT",
12616
+ worksheet: "WKS",
12617
+ ext: "EXT",
12618
+ codelab: "CODE",
12619
+ exit_ticket: "EXIT_TICKET"
12620
+ }[artifactType];
12621
+ const gateMode = kitGateType ? gateModeFor(gates, kitGateType) : "OFF";
12622
+ if (gateMode === "OFF") {
12623
+ return { artifactType, filename: meta.filename, content: finalContent, persona: meta.persona, thought: finalThought };
12624
+ }
12625
+ const injectReviewMeta = (doc, metaMap) => {
12626
+ const fm = doc.match(/^---\s*\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
12627
+ if (!fm) {
12628
+ return `---
12629
+ ${Object.entries(metaMap).map(([k, v]) => `${k}: "${v}"`).join("\n")}
12630
+ ---
12631
+
12632
+ ${doc}`;
12633
+ }
12634
+ let yaml = fm[1] ?? "";
12635
+ for (const [k, v] of Object.entries(metaMap)) {
12636
+ const re = new RegExp(`^${k}:.*$`, "m");
12637
+ if (re.test(yaml)) yaml = yaml.replace(re, `${k}: "${v}"`);
12638
+ else yaml += `
12639
+ ${k}: "${v}"`;
12640
+ }
12641
+ return doc.replace(fm[0], `---
12642
+ ${yaml.trim()}
12643
+ ---
12644
+ `);
12621
12645
  };
12646
+ if (gateMode === "HITL") {
12647
+ return {
12648
+ artifactType,
12649
+ filename: meta.filename,
12650
+ content: injectReviewMeta(finalContent, { review_status: "PENDING_REVIEW", review_decision: "PENDING" }),
12651
+ persona: meta.persona,
12652
+ thought: finalThought,
12653
+ pausedForReview: true,
12654
+ gateStatus: "awaiting_approval"
12655
+ };
12656
+ }
12657
+ const judgeObjectives = [{ code: "A1", description: `Artifact fulfills its pedagogical contract for topic "${topic}" with complete required sections`, bloomLevel: "Analyze" }];
12658
+ try {
12659
+ const verdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
12660
+ targetArtifactType: artifactType.toUpperCase(),
12661
+ lessonId: lessonId || "ON_DEMAND",
12662
+ expectedLanguage: language.toLowerCase().includes("viet") ? "vietnamese" : "english",
12663
+ targetObjectives: judgeObjectives,
12664
+ generatedContentJson: JSON.stringify({ content: finalContent, artifactType, lessonId: lessonId || "ON_DEMAND", title: displayTitle })
12665
+ });
12666
+ const passed = Boolean(verdict.passed) && verdict.score >= 85;
12667
+ if (passed) {
12668
+ return {
12669
+ artifactType,
12670
+ filename: meta.filename,
12671
+ content: injectReviewMeta(finalContent, {
12672
+ review_status: "APPROVED",
12673
+ reviewed_by: "@reviewer (LLM-as-Judge)",
12674
+ review_decision: "APPROVED"
12675
+ }),
12676
+ persona: meta.persona,
12677
+ thought: finalThought,
12678
+ judgeScore: verdict.score,
12679
+ judgeCritique: verdict.critique
12680
+ };
12681
+ }
12682
+ const repairFeedback = [verdict.autoRepairInstructions, ...verdict.weaknesses || []].filter(Boolean).join("\n");
12683
+ const repairedRaw = await streamCurriculumAIInference(
12684
+ [
12685
+ { role: "user", content: `You previously authored \`${meta.filename}\` and the academic Judge REJECTED it with the critique below.
12686
+ Rewrite the COMPLETE corrected document: fix every listed issue, keep the mandatory structure, frontmatter and all approved sections. Output 100% clean Markdown only.
12687
+
12688
+ JUDGE CRITIQUE:
12689
+ ${repairFeedback}
12690
+
12691
+ CURRENT DOCUMENT:
12692
+ ${finalContent}` }
12693
+ ],
12694
+ userPrompt,
12695
+ { ...options, systemPersona: systemPrompt },
12696
+ (chunk, type) => {
12697
+ if (onChunk) onChunk(chunk, type === "thought" ? "thought" : "content");
12698
+ }
12699
+ );
12700
+ const repairedExtract = extractThoughtAndContent(repairedRaw);
12701
+ const repaired = repairedExtract.content || repairedRaw;
12702
+ if (repaired && repaired.trim().length > 100) {
12703
+ const reVerdict = await LLMJudgeEngine.evaluateArtifactWithLLM({
12704
+ targetArtifactType: artifactType.toUpperCase(),
12705
+ lessonId: lessonId || "ON_DEMAND",
12706
+ expectedLanguage: language.toLowerCase().includes("viet") ? "vietnamese" : "english",
12707
+ targetObjectives: judgeObjectives,
12708
+ generatedContentJson: JSON.stringify({ content: repaired, artifactType, lessonId: lessonId || "ON_DEMAND", title: displayTitle })
12709
+ });
12710
+ if (reVerdict.passed && reVerdict.score >= 85) {
12711
+ return {
12712
+ artifactType,
12713
+ filename: meta.filename,
12714
+ content: injectReviewMeta(repaired, {
12715
+ review_status: "APPROVED",
12716
+ reviewed_by: "@reviewer (LLM-as-Judge)",
12717
+ review_decision: "APPROVED"
12718
+ }),
12719
+ persona: meta.persona,
12720
+ thought: repairedExtract.thought || "",
12721
+ judgeScore: reVerdict.score,
12722
+ judgeCritique: reVerdict.critique
12723
+ };
12724
+ }
12725
+ return {
12726
+ artifactType,
12727
+ filename: meta.filename,
12728
+ content: injectReviewMeta(repaired, {
12729
+ review_status: "NEEDS_REVISION",
12730
+ reviewed_by: "@reviewer (LLM-as-Judge)",
12731
+ review_decision: "NEEDS_REVISION"
12732
+ }),
12733
+ persona: meta.persona,
12734
+ thought: repairedExtract.thought || "",
12735
+ pausedForReview: true,
12736
+ gateStatus: "judge_needs_revision",
12737
+ judgeScore: reVerdict.score,
12738
+ judgeCritique: reVerdict.critique
12739
+ };
12740
+ }
12741
+ return {
12742
+ artifactType,
12743
+ filename: meta.filename,
12744
+ content: injectReviewMeta(finalContent, {
12745
+ review_status: "NEEDS_REVISION",
12746
+ reviewed_by: "@reviewer (LLM-as-Judge)",
12747
+ review_decision: "NEEDS_REVISION"
12748
+ }),
12749
+ persona: meta.persona,
12750
+ thought: finalThought,
12751
+ pausedForReview: true,
12752
+ gateStatus: "judge_needs_revision",
12753
+ judgeScore: verdict.score,
12754
+ judgeCritique: verdict.critique
12755
+ };
12756
+ } catch (judgeErr) {
12757
+ return {
12758
+ artifactType,
12759
+ filename: meta.filename,
12760
+ content: injectReviewMeta(finalContent, {
12761
+ review_status: "NEEDS_REVISION",
12762
+ review_decision: "NEEDS_REVISION",
12763
+ critique: `Judge unavailable (fail-closed): ${String(judgeErr?.message || judgeErr).slice(0, 300)}`
12764
+ }),
12765
+ persona: meta.persona,
12766
+ thought: finalThought,
12767
+ pausedForReview: true,
12768
+ gateStatus: "judge_error"
12769
+ };
12770
+ }
12622
12771
  } catch (error) {
12623
12772
  throw createAiInferenceError({
12624
12773
  errorCode: "ERR_AI_ALL_PROVIDERS_FAILED",
@@ -13374,43 +13523,15 @@ function safeParseJson(rawText) {
13374
13523
  return null;
13375
13524
  }
13376
13525
  }
13377
- async function readSseStream(reader, decoder, onData) {
13378
- let buffer = "";
13379
- while (true) {
13380
- const { done, value } = await reader.read();
13381
- if (done) break;
13382
- buffer += decoder.decode(value, { stream: true });
13383
- const lines = buffer.split("\n");
13384
- buffer = lines.pop() || "";
13385
- for (const line of lines) {
13386
- const trimmed = line.trim();
13387
- if (!trimmed || trimmed.startsWith(":")) continue;
13388
- if (trimmed === "data: [DONE]") return;
13389
- if (trimmed.startsWith("data: ")) {
13390
- const dataStr = trimmed.slice(6).trim();
13391
- if (dataStr) onData(dataStr);
13392
- }
13393
- }
13394
- }
13395
- }
13396
13526
  async function streamLLMWithFallback(systemPrompt, userPrompt, apiKeys = {}, onChunk) {
13397
- apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
13527
+ const alibabaKey = apiKeys.alibaba || apiKeys.dashscope || process.env.ALIBABA_API_KEY || process.env.DASHSCOPE_API_KEY;
13398
13528
  const nvidiaKey = apiKeys.nvidia || process.env.NVIDIA_API_KEY;
13399
- apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
13529
+ const deepseekKey = apiKeys.deepseek || process.env.DEEPSEEK_API_KEY;
13400
13530
  const openrouterKey = apiKeys.openrouter || process.env.OPENROUTER_API_KEY;
13401
- apiKeys.gemini || process.env.GEMINI_API_KEY;
13531
+ const geminiKey = apiKeys.gemini || process.env.GEMINI_API_KEY;
13402
13532
  const openrouterPreset = process.env.OPENROUTER_FREE_PRESET || "@preset/coding-free";
13403
- const messages = [
13404
- {
13405
- role: "system",
13406
- content: `${systemPrompt}
13407
-
13408
- THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on the core pedagogical trade-offs in 4-6 concise bullet points (under 120 words). Then output the JSON immediately.`
13409
- },
13410
- { role: "user", content: userPrompt }
13411
- ];
13533
+ const candidates = [];
13412
13534
  if (openrouterKey && isProviderEnabled("openrouter")) {
13413
- const t0 = Date.now();
13414
13535
  const orModels = [
13415
13536
  openrouterPreset,
13416
13537
  "openrouter/free",
@@ -13420,174 +13541,82 @@ THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on
13420
13541
  "minimax/minimax-m3:free",
13421
13542
  "cohere/north-mini-code:free"
13422
13543
  ].filter((m) => isModelAllowed(m));
13423
- const uniqueOrModels = Array.from(new Set(orModels));
13424
- for (const model of uniqueOrModels) {
13425
- try {
13426
- const controller = new AbortController();
13427
- const timeout = setTimeout(() => controller.abort(), 25e3);
13428
- const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
13429
- method: "POST",
13430
- headers: {
13431
- "Content-Type": "application/json",
13432
- "Authorization": `Bearer ${openrouterKey}`
13433
- },
13434
- body: JSON.stringify({
13435
- model,
13436
- messages,
13437
- stream: true,
13438
- temperature: 0.3
13439
- }),
13440
- signal: controller.signal
13441
- });
13442
- clearTimeout(timeout);
13443
- if (res.ok && res.body) {
13444
- const reader = res.body.getReader();
13445
- const decoder = new TextDecoder();
13446
- let fullContent = "";
13447
- await readSseStream(reader, decoder, (dataStr) => {
13448
- try {
13449
- const parsed = JSON.parse(dataStr);
13450
- const delta = parsed.choices?.[0]?.delta;
13451
- if (delta?.content) {
13452
- fullContent += delta.content;
13453
- onChunk?.(delta.content, "content");
13454
- }
13455
- } catch {
13456
- }
13457
- });
13458
- if (fullContent.trim()) {
13459
- console.log(`[curriculum-kit:LayerPrefill] \u2705 Tier 1 OpenRouter (${model}) streamed in ${Date.now() - t0}ms`);
13460
- return fullContent.trim();
13461
- }
13462
- }
13463
- } catch (e) {
13464
- console.warn(`[curriculum-kit:LayerPrefill] Tier 1 OpenRouter stream failed for ${model} in ${Date.now() - t0}ms:`, e?.message || e);
13465
- }
13544
+ for (const m of Array.from(new Set(orModels))) {
13545
+ candidates.push({ provider: "openrouter", model: m, apiKey: openrouterKey });
13466
13546
  }
13467
13547
  }
13468
13548
  if (nvidiaKey && isProviderEnabled("nvidia")) {
13469
- const t0 = Date.now();
13470
13549
  const nemotronModels = [
13471
13550
  "nvidia/nemotron-3-ultra-550b-a55b",
13472
13551
  "nvidia/nemotron-3.5-lightning-30b-a3b",
13473
13552
  "nvidia/nemotron-3-super-120b-a12b",
13474
13553
  "nvidia/llama-3.1-nemotron-70b-instruct"
13475
13554
  ].filter((m) => isModelAllowed(m));
13476
- for (const model of nemotronModels) {
13477
- try {
13478
- const controller = new AbortController();
13479
- const timeout = setTimeout(() => controller.abort(), 25e3);
13480
- const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
13481
- method: "POST",
13482
- headers: {
13483
- "Content-Type": "application/json",
13484
- "Authorization": `Bearer ${nvidiaKey}`
13485
- },
13486
- body: JSON.stringify({
13487
- model,
13488
- messages,
13489
- stream: true,
13490
- temperature: 0.2
13491
- }),
13492
- signal: controller.signal
13493
- });
13494
- clearTimeout(timeout);
13495
- if (res.ok && res.body) {
13496
- const reader = res.body.getReader();
13497
- const decoder = new TextDecoder();
13498
- let fullContent = "";
13499
- await readSseStream(reader, decoder, (dataStr) => {
13500
- try {
13501
- const parsed = JSON.parse(dataStr);
13502
- const delta = parsed.choices?.[0]?.delta;
13503
- if (delta?.content) {
13504
- fullContent += delta.content;
13505
- onChunk?.(delta.content, "content");
13506
- }
13507
- } catch {
13508
- }
13509
- });
13510
- if (fullContent.trim()) {
13511
- console.log(`[curriculum-kit:LayerPrefill] \u2705 Tier 2 NVIDIA NIM (${model}) streamed in ${Date.now() - t0}ms`);
13512
- return fullContent.trim();
13513
- }
13514
- }
13515
- } catch (e) {
13516
- console.warn(`[curriculum-kit:LayerPrefill] Tier 2 NVIDIA NIM stream failed for ${model} in ${Date.now() - t0}ms:`, e?.message || e);
13517
- }
13555
+ for (const m of nemotronModels) {
13556
+ candidates.push({ provider: "nvidia", model: m, apiKey: nvidiaKey });
13557
+ }
13558
+ }
13559
+ if (alibabaKey && isProviderEnabled("alibaba")) {
13560
+ const aliModels = ["qwen-plus", "qwen-turbo", "qwen3.7-plus"].filter((m) => isModelAllowed(m));
13561
+ for (const m of aliModels) {
13562
+ candidates.push({ provider: "alibaba", model: m, apiKey: alibabaKey });
13563
+ }
13564
+ }
13565
+ if (deepseekKey && isProviderEnabled("deepseek")) {
13566
+ const dsModels = ["deepseek-chat", "deepseek-coder"].filter((m) => isModelAllowed(m));
13567
+ for (const m of dsModels) {
13568
+ candidates.push({ provider: "deepseek", model: m, apiKey: deepseekKey });
13569
+ }
13570
+ }
13571
+ if (geminiKey && (isProviderEnabled("gemini") || isProviderEnabled("google"))) {
13572
+ const gModels = ["gemini-1.5-flash", "gemini-2.0-flash", "gemini-1.5-pro"].filter((m) => isModelAllowed(m));
13573
+ for (const m of gModels) {
13574
+ candidates.push({ provider: "google", model: m, apiKey: geminiKey });
13518
13575
  }
13519
13576
  }
13520
13577
  const fallbackChain = getDesignatedFallbackChain();
13521
13578
  for (const target of fallbackChain) {
13522
- const { provider, model } = target;
13523
- if (provider === "nvidia" && nvidiaKey && isProviderEnabled("nvidia")) {
13524
- try {
13525
- const res = await fetch("https://integrate.api.nvidia.com/v1/chat/completions", {
13526
- method: "POST",
13527
- headers: {
13528
- "Content-Type": "application/json",
13529
- "Authorization": `Bearer ${nvidiaKey}`
13530
- },
13531
- body: JSON.stringify({
13532
- model,
13533
- messages,
13534
- stream: true,
13535
- temperature: 0.2
13536
- })
13537
- });
13538
- if (res.ok && res.body) {
13539
- const reader = res.body.getReader();
13540
- const decoder = new TextDecoder();
13541
- let fullContent = "";
13542
- await readSseStream(reader, decoder, (dataStr) => {
13543
- try {
13544
- const parsed = JSON.parse(dataStr);
13545
- const delta = parsed.choices?.[0]?.delta;
13546
- if (delta?.content) {
13547
- fullContent += delta.content;
13548
- onChunk?.(delta.content, "content");
13549
- }
13550
- } catch {
13551
- }
13552
- });
13553
- if (fullContent.trim()) return fullContent.trim();
13579
+ if (!candidates.some((c) => c.provider === target.provider && c.model === target.model)) {
13580
+ candidates.push({
13581
+ provider: target.provider,
13582
+ model: target.model,
13583
+ apiKey: apiKeys[target.provider]
13584
+ });
13585
+ }
13586
+ }
13587
+ const systemInstructions = `${systemPrompt}
13588
+
13589
+ THINKING DIRECTIVE: Keep reasoning concise, structured, and focused strictly on the core pedagogical trade-offs in 4-6 concise bullet points (under 120 words). Then output the JSON immediately.`;
13590
+ for (const candidate of candidates) {
13591
+ const t0 = Date.now();
13592
+ try {
13593
+ const modelInstance = getAIModel({
13594
+ provider: candidate.provider,
13595
+ modelName: candidate.model,
13596
+ apiKey: candidate.apiKey
13597
+ });
13598
+ const streamResult = ai.streamText({
13599
+ model: modelInstance,
13600
+ system: systemInstructions,
13601
+ prompt: userPrompt,
13602
+ temperature: 0.2,
13603
+ abortSignal: AbortSignal.timeout(25e3)
13604
+ });
13605
+ let fullContent = "";
13606
+ for await (const part of streamResult.fullStream) {
13607
+ if (part.type === "reasoning-delta") {
13608
+ onChunk?.(part.text, "thought");
13609
+ } else if (part.type === "text-delta") {
13610
+ fullContent += part.text;
13611
+ onChunk?.(part.text, "content");
13554
13612
  }
13555
- } catch {
13556
13613
  }
13557
- } else if (provider === "openrouter" && openrouterKey && isProviderEnabled("openrouter")) {
13558
- try {
13559
- const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
13560
- method: "POST",
13561
- headers: {
13562
- "Content-Type": "application/json",
13563
- "Authorization": `Bearer ${openrouterKey}`
13564
- },
13565
- body: JSON.stringify({
13566
- model,
13567
- messages,
13568
- stream: true,
13569
- temperature: 0.2
13570
- })
13571
- });
13572
- if (res.ok && res.body) {
13573
- const reader = res.body.getReader();
13574
- const decoder = new TextDecoder();
13575
- let fullContent = "";
13576
- await readSseStream(reader, decoder, (dataStr) => {
13577
- try {
13578
- const parsed = JSON.parse(dataStr);
13579
- const delta = parsed.choices?.[0]?.delta;
13580
- if (delta?.content) {
13581
- fullContent += delta.content;
13582
- onChunk?.(delta.content, "content");
13583
- }
13584
- } catch {
13585
- }
13586
- });
13587
- if (fullContent.trim()) return fullContent.trim();
13588
- }
13589
- } catch {
13614
+ if (fullContent.trim()) {
13615
+ console.log(`[curriculum-kit:VercelAI] \u2705 ${candidate.provider} (${candidate.model}) streamed in ${Date.now() - t0}ms`);
13616
+ return fullContent.trim();
13590
13617
  }
13618
+ } catch (e) {
13619
+ console.warn(`[curriculum-kit:VercelAI] Candidate ${candidate.provider} (${candidate.model}) failed in ${Date.now() - t0}ms:`, e?.message || e);
13591
13620
  }
13592
13621
  }
13593
13622
  return null;
@@ -14454,7 +14483,7 @@ template_contract: "artifact-template-v1"
14454
14483
  async function withRateLimitBackoff(options) {
14455
14484
  let attempt = 1;
14456
14485
  try {
14457
- const meta = workflowPkg.getStepMetadata();
14486
+ const meta = workflow.getStepMetadata();
14458
14487
  if (meta && typeof meta.attempt === "number") {
14459
14488
  attempt = meta.attempt;
14460
14489
  }
@@ -14470,7 +14499,7 @@ async function withRateLimitBackoff(options) {
14470
14499
  if (isRateLimited || isTransientError) {
14471
14500
  const retryAfterSeconds = Math.min(120, Math.pow(2, attempt) * 2);
14472
14501
  const reason = isRateLimited ? "Rate limit exceeded (429)" : `Transient server error (${status || "network"})`;
14473
- throw new workflowPkg.RetryableError(`[${options.stepName}] ${reason}. Retrying attempt ${attempt + 1}...`, {
14502
+ throw new workflow.RetryableError(`[${options.stepName}] ${reason}. Retrying attempt ${attempt + 1}...`, {
14474
14503
  retryAfter: `${retryAfterSeconds}s`
14475
14504
  });
14476
14505
  }
@@ -14755,10 +14784,10 @@ var approvalPayloadSchema = zod.z.object({
14755
14784
  feedback: zod.z.string().optional().describe("Actionable feedback or required changes if rejected"),
14756
14785
  timestamp: zod.z.string().optional().describe("ISO timestamp of the approval action")
14757
14786
  });
14758
- var hookFactory = workflowPkg__namespace.defineHook || workflowPkg__namespace.createHook || ((opts) => opts);
14759
- var lessonApprovalHook = hookFactory({
14760
- schema: approvalPayloadSchema
14761
- });
14787
+ var lessonApprovalHook = workflow.defineHook();
14788
+ function approvalHookToken(projectId, lessonId, artifactType) {
14789
+ return `approval:${projectId}:${lessonId}:${artifactType}`;
14790
+ }
14762
14791
 
14763
14792
  // src/standards/standardsCoverageGate.ts
14764
14793
  function resolveStatementRef(ref, packs) {
@@ -15108,9 +15137,9 @@ async function generateMilestoneWorkflow(input) {
15108
15137
  } else if (lessonGate === "HITL") {
15109
15138
  const hookToken = "gate:lesson:" + milestoneId;
15110
15139
  console.log(" \u23F8 [Gate] LESSON awaiting HUMAN review (HITL) \u2014 hook " + hookToken);
15111
- const workflowPkg2 = await import('workflow');
15112
- const createHook2 = workflowPkg2.createHook;
15113
- const hook = createHook2({ token: hookToken });
15140
+ const workflowPkg = await import('workflow');
15141
+ const createHook = workflowPkg.createHook;
15142
+ const hook = createHook({ token: hookToken });
15114
15143
  const review = await hook;
15115
15144
  console.log(" \u2705 [Gate] Human verdict: " + (review.approved ? "APPROVED" : "REJECTED") + (review.feedback ? " \u2014 " + review.feedback : ""));
15116
15145
  if (!review.approved) {
@@ -15130,7 +15159,7 @@ async function generateMilestoneWorkflow(input) {
15130
15159
  standardsContext: standardsContext || void 0,
15131
15160
  modelOptions: input.modelOptions
15132
15161
  });
15133
- const hook2 = createHook2({ token: hookToken + ":v2" });
15162
+ const hook2 = createHook({ token: hookToken + ":v2" });
15134
15163
  const review2 = await hook2;
15135
15164
  console.log(" \u2705 [Gate] Human verdict (v2): " + (review2.approved ? "APPROVED" : "REJECTED"));
15136
15165
  if (!review2.approved) {
@@ -15318,7 +15347,7 @@ async function generateRoadmapWorkflow(input) {
15318
15347
  }
15319
15348
  }
15320
15349
  if (i + batchSize < milestones.length) {
15321
- await workflowPkg.sleep("2s");
15350
+ await workflow.sleep("2s");
15322
15351
  }
15323
15352
  }
15324
15353
  const successfulMilestones = milestoneOutcomes.filter((m) => m.status === "FULFILLED").length;
@@ -16740,6 +16769,36 @@ var MediaLedger = class {
16740
16769
  doneCount() {
16741
16770
  return this.listByStatus("done").length;
16742
16771
  }
16772
+ /**
16773
+ * Scan artifact content và seed pending entries cho mọi placeholder [IMAGE: slug].
16774
+ * Được gọi sau khi agent sinh artifact (trước khi Curator duyệt) — đây là cầu nối
16775
+ * duy nhất giữa "agent chèn placeholder" và "sổ cái có entry để Curator/Fulfiller xử".
16776
+ * Idempotent: slug đã có entry active (pending/approved/done) → giữ nguyên;
16777
+ * chỉ re-describe khi entry trước bị rejected/failed.
16778
+ */
16779
+ seedFromArtifact(artifactId, content) {
16780
+ const seeded = [];
16781
+ const re = /\[IMAGE\s*:\s*([a-z0-9][a-z0-9-]*)\s*\]/gi;
16782
+ const seen = /* @__PURE__ */ new Set();
16783
+ let m;
16784
+ while ((m = re.exec(content)) !== null) {
16785
+ const slug = m[1].toLowerCase();
16786
+ if (seen.has(slug)) continue;
16787
+ seen.add(slug);
16788
+ const existing = this.entries.get(slug);
16789
+ const isActive = existing && (existing.status === "pending" || existing.status === "approved" || existing.status === "done");
16790
+ if (isActive) continue;
16791
+ seeded.push(this.upsert({
16792
+ slug,
16793
+ artifactId,
16794
+ name: slug.replace(/^(img-|image-)/, "").replace(/-/g, " "),
16795
+ description: `Placeholder ch\xE8n b\u1EDFi agent trong artifact ${artifactId} \u2014 ch\u1EDD Curator enrich (m\xF4 t\u1EA3 g\u1ED1c ch\u01B0a c\xF3).`,
16796
+ mode: "generate",
16797
+ status: "pending"
16798
+ }));
16799
+ }
16800
+ return seeded;
16801
+ }
16743
16802
  };
16744
16803
 
16745
16804
  // src/media/curator.ts
@@ -17154,6 +17213,7 @@ exports.WorksheetSchema = WorksheetSchema;
17154
17213
  exports.activityTools = activityTools;
17155
17214
  exports.analystTools = analystTools;
17156
17215
  exports.analyzeProjectCreationIntent = analyzeProjectCreationIntent;
17216
+ exports.approvalHookToken = approvalHookToken;
17157
17217
  exports.approvalPayloadSchema = approvalPayloadSchema;
17158
17218
  exports.assertAcyclic = assertAcyclic;
17159
17219
  exports.assessorTools = assessorTools;