@dzhechkov/p-replicator 1.10.4 → 1.13.1
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/.dz-manifest.json +312 -76
- package/CHANGELOG.md +232 -0
- package/MULTIPLATFORM_ROADMAP.md +1 -1
- package/README/eng/01_quickstart.md +3 -3
- package/README/eng/02_user_guide.md +1 -1
- package/README/eng/03_admin_guide.md +2 -2
- package/README/eng/04_api_reference.md +11 -5
- package/README/eng/05_architecture.md +1 -1
- package/README/eng/README.md +2 -1
- package/README/ru/01_quickstart.md +3 -3
- package/README/ru/02_user_guide.md +1 -1
- package/README/ru/03_admin_guide.md +2 -2
- package/README/ru/04_api_reference.md +11 -5
- package/README/ru/05_architecture.md +1 -1
- package/README/ru/README.md +2 -1
- package/README/ru/html/index.html +9 -9
- package/README.md +278 -39
- package/package.json +5 -4
- package/sbom.json +665 -75
- package/scripts/check-pipeline-gaps.sh +413 -0
- package/src/commands/doctor.js +94 -4
- package/src/commands/init.js +1 -1
- package/src/rule-components.json +15 -0
- package/src/utils.js +35 -11
- package/templates/.claude/agents/harvest-coordinator.md +10 -1
- package/templates/.claude/agents/product-discoverer.md +38 -0
- package/templates/.claude/agents/replicate-coordinator.md +11 -1
- package/templates/.claude/commands/feature.md +81 -9
- package/templates/.claude/commands/go.md +9 -0
- package/templates/.claude/commands/harvest.md +39 -3
- package/templates/.claude/commands/myinsights.md +21 -26
- package/templates/.claude/commands/replicate.md +171 -37
- package/templates/.claude/commands/start.md +29 -0
- package/templates/.claude/hooks/capture-source-path.cjs +795 -0
- package/templates/.claude/hooks/check-canon.cjs +493 -0
- package/templates/.claude/hooks/check-embed-contract.cjs +374 -0
- package/templates/.claude/hooks/check-external-deps.cjs +288 -0
- package/templates/.claude/hooks/check-file-ownership.cjs +424 -0
- package/templates/.claude/hooks/check-handoff-manifest.cjs +367 -0
- package/templates/.claude/hooks/check-job-contract.cjs +501 -0
- package/templates/.claude/hooks/check-look-origin.cjs +240 -0
- package/templates/.claude/hooks/check-look-trace.cjs +385 -0
- package/templates/.claude/hooks/check-metric-source.cjs +296 -0
- package/templates/.claude/hooks/check-model-cost.cjs +470 -0
- package/templates/.claude/hooks/check-ports.cjs +434 -24
- package/templates/.claude/hooks/check-source-version.cjs +312 -0
- package/templates/.claude/hooks/check-swarm-receipts.cjs +197 -0
- package/templates/.claude/hooks/check-webhook-contract.cjs +535 -0
- package/templates/.claude/hooks/session-insights.cjs +158 -25
- package/templates/.claude/hooks/statusline.cjs +2 -2
- package/templates/.claude/hooks/write-insight.cjs +253 -0
- package/templates/.claude/rules/cost-of-detection-ladder.md +96 -0
- package/templates/.claude/rules/docker-ports.md +41 -19
- package/templates/.claude/rules/embeddable-widget.md +73 -0
- package/templates/.claude/rules/feature-lifecycle.md +13 -3
- package/templates/.claude/rules/honest-configuration.md +54 -0
- package/templates/.claude/rules/incoming-webhooks.md +99 -0
- package/templates/.claude/rules/insights-capture.md +10 -5
- package/templates/.claude/rules/long-running-job.md +73 -0
- package/templates/.claude/rules/model-call-cost.md +85 -0
- package/templates/.claude/rules/replicate-pipeline.md +123 -52
- package/templates/.claude/rules/skill-interface-protocol.md +1 -0
- package/templates/.claude/rules/swarm-file-evidence.md +46 -0
- package/templates/.claude/settings.json +13 -1
- package/templates/.claude/skills/brutal-honesty-review/SKILL.md +9 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/SKILL.md +4 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/03-generate-p0.md +46 -1
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/04-generate-p1.md +7 -1
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/modules/06-package-deliver.md +20 -2
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/claude-md-strategy.md +7 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/automation-commands.md +17 -0
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle-ent.md +43 -5
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/feature-lifecycle.md +43 -7
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/start-command.md +19 -1
- package/templates/.claude/skills/cc-toolkit-generator-enhanced/references/templates/swarm-file-evidence.md +151 -0
- package/templates/.claude/skills/goap-research-ed25519/SKILL.md +37 -22
- package/templates/.claude/skills/goap-research-ed25519/references/negative-results.md +94 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/check_report_evidence.py +368 -4
- package/templates/.claude/skills/goap-research-ed25519/scripts/ed25519_verifier.py +122 -5
- package/templates/.claude/skills/goap-research-ed25519/scripts/evidence_fetch.py +33 -16
- package/templates/.claude/skills/goap-research-ed25519/scripts/quote_provenance.py +342 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_ed25519_verifier.py +60 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_evidence_provenance.py +139 -6
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_quote_provenance.py +274 -0
- package/templates/.claude/skills/goap-research-ed25519/scripts/test_suite_completeness.py +2 -1
- package/templates/.claude/skills/knowledge-extractor/SKILL.md +4 -0
- package/templates/.claude/skills/knowledge-extractor/modules/01-agent-review.md +16 -5
- package/templates/.claude/skills/pipeline-forge/SKILL.md +18 -23
- package/templates/.claude/skills/pipeline-forge/examples/replicate-analysis.md +7 -2
- package/templates/.claude/skills/pipeline-forge/references/patterns-catalog.md +19 -1
- package/templates/.claude/skills/pipeline-forge/references/self-extracted-patterns.md +17 -6
- package/templates/.claude/skills/pipeline-forge/references/skill-anatomy.md +0 -1
- package/templates/.claude/skills/reverse-engineering-unicorn/modules/025-cjm-prototype.md +21 -1
- package/templates/.claude/skills/sparc-prd-mini/SKILL.md +234 -716
- package/tests/e2e/lifecycle.test.js +55 -9
- package/tests/e2e/packed-insights-writer.test.js +308 -0
- package/tests/fixtures/prep-traceability-fixture/docs/features/order-refund/01_specification.md +29 -0
- package/tests/fixtures/prep-traceability-fixture/docs/features/order-refund/02_pseudocode.md +57 -0
- package/tests/snapshot/baseline.json +72 -46
- package/tests/snapshot/templates.test.js +47 -0
- package/tests/unit/absence-is-not-emptiness.test.js +15 -1
- package/tests/unit/capture-source-path.test.js +492 -0
- package/tests/unit/check-canon.test.js +403 -0
- package/tests/unit/check-embed-contract.test.js +422 -0
- package/tests/unit/check-external-deps.test.js +363 -0
- package/tests/unit/check-file-ownership.test.js +388 -0
- package/tests/unit/check-handoff-manifest.test.js +410 -0
- package/tests/unit/check-job-contract.test.js +514 -0
- package/tests/unit/check-look-origin.test.js +180 -0
- package/tests/unit/check-look-trace.test.js +420 -0
- package/tests/unit/check-metric-source.test.js +325 -0
- package/tests/unit/check-model-cost.test.js +425 -0
- package/tests/unit/check-pipeline-gaps.test.js +94 -0
- package/tests/unit/check-ports.test.js +773 -2
- package/tests/unit/check-source-version.test.js +344 -0
- package/tests/unit/check-swarm-receipts.test.js +231 -0
- package/tests/unit/check-webhook-contract.test.js +536 -0
- package/tests/unit/db-port-rule.test.js +43 -6
- package/tests/unit/detection-ladder-contract.test.js +302 -0
- package/tests/unit/detection-ladder-registry.test.js +52 -0
- package/tests/unit/doctor-insight-flow.test.js +315 -0
- package/tests/unit/external-dependency-check.test.js +19 -19
- package/tests/unit/generator-swarm-contract.test.js +287 -0
- package/tests/unit/guard-honest-input-meta.test.js +64 -0
- package/tests/unit/honest-failure-rules.test.js +574 -0
- package/tests/unit/hooks-project-anchored.test.js +67 -3
- package/tests/unit/insights-docs-tell-the-truth.test.js +52 -31
- package/tests/unit/insights-dz-delegation.test.js +197 -0
- package/tests/unit/insights-writer.test.js +285 -0
- package/tests/unit/look-phase-contract.test.js +231 -0
- package/tests/unit/negative-conclusion-gate.test.js +300 -0
- package/tests/unit/quote-provenance.test.js +122 -0
- package/tests/unit/shipped-suite-context.test.js +3 -1
- package/tests/unit/traceability-machine-ids.test.js +413 -0
- package/tests/unit/traceability-negative-fixture.test.js +322 -0
- package/tests/unit/utils.test.js +40 -2
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Встраиваемый виджет: он живёт на ЧУЖОЙ странице
|
|
2
|
+
|
|
3
|
+
Действует, когда поставляемое — кусок, который клиент вставляет к себе: сборщик отзывов, чат-пузырь,
|
|
4
|
+
калькулятор. Там виджет есть ПРОДУКТ ЦЕЛИКОМ: не работает у клиента — не работает ничего.
|
|
5
|
+
|
|
6
|
+
## Своя страница — не проверка
|
|
7
|
+
|
|
8
|
+
**Проверка виджета ОБЯЗАНА выполняться на странице ЧУЖОГО origin** (origin = схема+хост+порт,
|
|
9
|
+
браузерное определение «другого сайта»). Другого порта достаточно: `http://localhost:8099` чужой
|
|
10
|
+
для `http://localhost:3000` и даёт настоящий предполётный запрос.
|
|
11
|
+
|
|
12
|
+
«Открыли свою демо-страницу, виджет отрисовался» — НЕ проверка. На своей странице origin совпадает
|
|
13
|
+
(предполётного запроса нет вовсе), чужого CSS нет, чужой политики безопасности нет: ни один из трёх
|
|
14
|
+
классов отказа там не может проявиться. Зелёный результат получен на единственной странице, чьё
|
|
15
|
+
поведение не имеет значения.
|
|
16
|
+
|
|
17
|
+
Механизм: **условия отказа принадлежат чужой странице, а тестируется своя.** Тот же дефект даёт
|
|
18
|
+
подтверждение развёртывания обращением к `localhost` — проверка обязана пользоваться адресом,
|
|
19
|
+
который система ВЫДАЛА, а не тем, который знает сама.
|
|
20
|
+
|
|
21
|
+
## Три класса отказа
|
|
22
|
+
|
|
23
|
+
Набор ЗАКРЫТЫЙ и ОБЯЗАТЕЛЬНЫЙ: виджет, переживший чужой CSS и умерший на чужом CSP, у клиента не
|
|
24
|
+
работает. Первый и третий отказывают при полностью зелёной проверке у вас.
|
|
25
|
+
|
|
26
|
+
| Класс | Признак у клиента | Лечение |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| `перекрёстный-запрос` | виджет виден, данные не идут; в консоли клиента `blocked by CORS policy`, а в вашем журнале запрос ЕСТЬ и отвечен 200 — ответ отбросил браузер | отвечать `Access-Control-Allow-Origin` с origin ХОЗЯИНА из явного списка; `OPTIONS` → 204 с `Allow-Methods`/`Allow-Headers`. С `credentials` джокер `*` НЕЛЕГАЛЕН, браузер отклонит |
|
|
29
|
+
| `протечка-стилей` | вёрстка едет только у клиента и у каждого по-своему: чужие reset, `* { box-sizing }`, `img { width: 100% }`, война `z-index`. Обратное направление — тоже отказ: ваши глобальные селекторы ломают хозяйскую страницу | изоляция ГРАНИЦЕЙ, а не специфичностью: Shadow DOM либо iframe; внутри `all: initial` на корне и свои единицы вместо унаследованных |
|
|
30
|
+
| `политика-безопасности` | виджет не появляется ВООБЩЕ; в консоли `Refused to load … violates the following Content Security Policy directive` | никаких инлайновых `<script>`/`<style>` (не требовать `unsafe-inline`); опубликовать точный список директив, который хозяин обязан разрешить (`script-src`, `connect-src`, `frame-src`, `img-src`); проверять ПОД ограничительным CSP |
|
|
31
|
+
|
|
32
|
+
**Оснастка, воспроизводящая все три:** страница по HTTP на ДРУГОМ порту, вставляющая виджет по
|
|
33
|
+
ПУБЛИЧНОМУ адресу, который выдало развёртывание, отдающая ограничительный `Content-Security-Policy`
|
|
34
|
+
и несущая враждебный CSS.
|
|
35
|
+
|
|
36
|
+
## Артефакт и ворота
|
|
37
|
+
|
|
38
|
+
`docs/embed-contract.md` — объявление плюс квитанция, по строке на каждый класс, и в каждой строке
|
|
39
|
+
ДОКАЗАТЕЛЬСТВО с адресом. Точная форма полей и закрытые списки значений — в шапке проверки:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node .claude/hooks/check-embed-contract.cjs .
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`0` все три класса проверены и каждое доказательство называет ЧУЖОЙ origin · `1` дефект ДОКАЗАН и
|
|
46
|
+
назван (проверка на своём origin, страница `file://`, пропущенный класс, `НЕ ПРОВЕРЕН` под вывеской
|
|
47
|
+
выполненной проверки, доказательство без адреса, `credentials` вместе с `*`) · `2` **проверка НЕ
|
|
48
|
+
ВЫПОЛНЕНА** (нет контракта, нераспознанное значение, неразбираемый адрес, либо законные ответы «не
|
|
49
|
+
встраивается» и «НЕ ВЫПОЛНЕНА с причиной»). Код `2` никогда не значит «всё в порядке».
|
|
50
|
+
|
|
51
|
+
## Честная разметка слоя
|
|
52
|
+
|
|
53
|
+
Слои — по [`cost-of-detection-ladder`](./cost-of-detection-ladder.md).
|
|
54
|
+
|
|
55
|
+
**Слой 1 (детерминированно):** доказательство называет origin, и он не ваш; все три класса названы;
|
|
56
|
+
пара `credentials` + `*` отвергнута. Это проверка ДЕКЛАРАЦИИ.
|
|
57
|
+
|
|
58
|
+
**Слой 3–4 (остаётся суждением, и сузить нечем):** цел ли виджет на враждебной странице; те ли это
|
|
59
|
+
директивы CSP, которые нужны хозяину; не отражает ли список разрешённых origin произвольный origin
|
|
60
|
+
обратно. Детерминированной половины здесь быть НЕ МОЖЕТ по названной причине: у пакета ноль
|
|
61
|
+
зависимостей и нет браузера, а вердикт выносит только настоящий браузер на настоящей чужой странице.
|
|
62
|
+
|
|
63
|
+
**Нового семейства идентификаторов НЕТ, и это решение.** `FR-LOOK-nnn` отвечает «снятое с источника
|
|
64
|
+
доехало до спецификации?»; здесь снимать нечего — обязательство рождается из топологии доставки, а
|
|
65
|
+
не из внешности источника, и статусы `СНЯТ`/`НЕ ИЗМЕРЕНО` к нему неприменимы. Наблюдение о том, КАК
|
|
66
|
+
встраивается сам источник (iframe или Shadow DOM), — законная строка `FR-LOOK-nnn` оси `облик`; три
|
|
67
|
+
класса отказа — не она.
|
|
68
|
+
|
|
69
|
+
## Самопроверка
|
|
70
|
+
|
|
71
|
+
1. Назови origin страницы, где виджет проверяли. Совпал с origin виджета? Проверки не было.
|
|
72
|
+
2. Та страница отдавала CSP и враждебный CSS? Нет — проверены не те условия.
|
|
73
|
+
3. Адрес виджета там — тот, который ВЫДАЛО развёртывание, или тот, который ты знал заранее?
|
|
@@ -89,13 +89,21 @@ After 3 retries with 🔴, halt and surface to user.
|
|
|
89
89
|
**Strategy:** maximum parallelism via `Task` tool.
|
|
90
90
|
|
|
91
91
|
1. Identify independent work units from Phase 1's Architecture
|
|
92
|
-
2.
|
|
93
|
-
3.
|
|
94
|
-
4. Coordinator
|
|
92
|
+
2. Allocate one `RUN_ID`, a unique `WORK_UNIT_ID`, and an absolute `TRACE_PATH` per unit
|
|
93
|
+
3. Spawn one Task per unit; each writes its substantive trace before its one-line pointer
|
|
94
|
+
4. Coordinator validates every trace before merge/integration
|
|
95
95
|
5. Run full test suite
|
|
96
96
|
|
|
97
97
|
**Quality gate:** tests pass, lint clean, build succeeds.
|
|
98
98
|
|
|
99
|
+
### Positive file receipt (required)
|
|
100
|
+
|
|
101
|
+
Each unit gets a unique `WORK_UNIT_ID` and unique absolute `TRACE_PATH`. Its worker MUST write a
|
|
102
|
+
substantive body ending in `Status: completed` or `Status: failed` to `TRACE_PATH` before its one-line
|
|
103
|
+
pointer. Before integration, the coordinator MUST verify a regular, non-symlink, substantive,
|
|
104
|
+
post-launch file with a terminal status. Narrative/chat/silence is never a receipt; any invalid receipt
|
|
105
|
+
MUST block merge/completion. Full rule and bounded exception: [`rules/swarm-file-evidence.md`](./swarm-file-evidence.md).
|
|
106
|
+
|
|
99
107
|
## Phase 4: REVIEW (brutal-honesty-review)
|
|
100
108
|
|
|
101
109
|
**Skill:** `.claude/skills/brutal-honesty-review/SKILL.md`
|
|
@@ -118,6 +126,8 @@ After 3 retries with 🔴, halt and surface to user.
|
|
|
118
126
|
| Tests + lint + build | 3 | Re-run, max 3 attempts |
|
|
119
127
|
| No `blocker` findings | 4 | Loop Phase 4 until clean |
|
|
120
128
|
|
|
129
|
+
Place each gate on the strongest reliable enforcement layer; see [`cost-of-detection-ladder`](./cost-of-detection-ladder.md).
|
|
130
|
+
|
|
121
131
|
## Commit Discipline
|
|
122
132
|
|
|
123
133
|
- After Phase 1: `docs(<feature>): SPARC plan`
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Honest Configuration
|
|
2
|
+
|
|
3
|
+
## Rule
|
|
4
|
+
|
|
5
|
+
A value that controls external output, access, limits, routing, or an authoritative measurement must
|
|
6
|
+
not become a plausible or permissive result when its meaning is absent or unproven. Refuse or expose
|
|
7
|
+
unknown; never manufacture health.
|
|
8
|
+
|
|
9
|
+
## Mechanics
|
|
10
|
+
|
|
11
|
+
### Substitution axis
|
|
12
|
+
|
|
13
|
+
Absence is not permission to invent a runtime value. Validate required values at the boundary and
|
|
14
|
+
derive degradation from the value actually obtained, even when no failure-only sentinel was set.
|
|
15
|
+
|
|
16
|
+
### Interpretation axis
|
|
17
|
+
|
|
18
|
+
Keep `undefined` distinct from `''`. Validate empty, misspelled, unmapped, and authority-derived values
|
|
19
|
+
against a closed set in versioned code. Environment variables may select a code-owned variant; they
|
|
20
|
+
must not define the allowlist. A declared input that no decision reads is fail-open-by-omission.
|
|
21
|
+
|
|
22
|
+
| Case | Observable signal | Required response |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| CFG-S1 | Required runtime value is absent | REFUSE and name the external consequence; no plausible default. |
|
|
25
|
+
| CFG-S2 | Obtained value is invalid although the failure sentinel is unset | REFUSE from the obtained value. |
|
|
26
|
+
| CFG-I1 | Value is `undefined` | REFUSE or UNKNOWN; preserve the absent state. |
|
|
27
|
+
| CFG-I2 | Value is the empty string `''` | REFUSE; do not collapse it into `undefined` or unrestricted. |
|
|
28
|
+
| CFG-I3 | Variant is misspelled, unknown, or unmapped | REFUSE; list the code-owned recognized variants. |
|
|
29
|
+
| CFG-I4 | Source of truth is unreachable | UNKNOWN or REFUSE; do not use cached permissive meaning. |
|
|
30
|
+
| CFG-I5 | Declared allowlist/config input is never read by the decision | REFUSE and wire the decision to the input. |
|
|
31
|
+
| CFG-I6 | Empty CIDR becomes `/0`, allowlist is empty, `BASE_URL='/'`, or tariff is unmapped | REFUSE; no unlimited access or plausible output. |
|
|
32
|
+
| CFG-I7 | Ratio denominator is zero (`0/0`) | UNAVAILABLE or UNKNOWN; render empty with the reason, never `0%`. |
|
|
33
|
+
| CFG-I8 | Allowlist or recognized-variant universe comes from environment | CODE-OWNED closed set; environment selects only. |
|
|
34
|
+
|
|
35
|
+
## Bounded exception
|
|
36
|
+
|
|
37
|
+
A named build phase may use a substitute only when that phase cannot emit or publish the external
|
|
38
|
+
result; generic “non-production” is not a boundary. An optional dependency may fall back only when
|
|
39
|
+
its absence cannot alter the governed external output, access, limit, route, or measurement.
|
|
40
|
+
|
|
41
|
+
## Observable violation → replacement
|
|
42
|
+
|
|
43
|
+
| Observable violation | Required replacement |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Missing URL/project name becomes localhost, `/`, or an inferred name | Validate at the boundary and refuse with the affected output named. |
|
|
46
|
+
| Empty/unknown access or limit value becomes unrestricted | Reject before the access, routing, or limiting decision. |
|
|
47
|
+
| Failure is logged but the invalid obtained value still emits a healthy result | Compute status from the value/outcome and stop the emitting action. |
|
|
48
|
+
| Undefined measurement renders as numeric zero | Render unavailable/empty and preserve why it could not be measured. |
|
|
49
|
+
|
|
50
|
+
## Self-check
|
|
51
|
+
|
|
52
|
+
For each governed value, enumerate absent, empty, invalid, unknown, and unreachable states beside one
|
|
53
|
+
explicit valid control. Trace the value into the decision that emits output. If any bad state reaches
|
|
54
|
+
a default, `ALLOW`, unlimited behavior, `/0`, or `0%` for `0/0`, the boundary is fail-open.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Входящие вебхуки: событие приходит дважды, и приходит от кого угодно
|
|
2
|
+
|
|
3
|
+
Действует, когда в продукт ЗВОНЯТ снаружи: платёжный провайдер, биллинг, партнёрская сеть. Где
|
|
4
|
+
вебхук несёт деньги, он и есть учётная запись.
|
|
5
|
+
|
|
6
|
+
## Одна доставка — не одно событие
|
|
7
|
+
|
|
8
|
+
**Платёжные системы доставляют одно событие НЕСКОЛЬКО РАЗ по построению.** Гарантия — «не менее
|
|
9
|
+
одного раза», а не «ровно один раз»: таймаут, 500, оборванный ACK — и событие приезжает снова. Это
|
|
10
|
+
контракт провайдера, не сбой.
|
|
11
|
+
|
|
12
|
+
Обработчик без ключа повторности начисляет партнёру комиссию дважды, и **никто этого не замечает**:
|
|
13
|
+
оба начисления по отдельности законны: ни ошибки, ни упавшего запроса — отказ без симптома, только
|
|
14
|
+
неверные деньги.
|
|
15
|
+
|
|
16
|
+
**Ключ повторности ОБЯЗАН быть НАЗВАН, а не подразумеваться.** «Сделаем идемпотентно» — намерение,
|
|
17
|
+
не ключ. Названы должны быть три:
|
|
18
|
+
|
|
19
|
+
1. **ПОЛЕ** — тождество события У ОТПРАВИТЕЛЯ (`event.id`): одинаково во всех попытках доставки
|
|
20
|
+
одного события, различно у двух разных. Значение, выданное получателем на приёме, различно на
|
|
21
|
+
каждой доставке — им повтор не узнать никогда.
|
|
22
|
+
2. **МЕСТО** — таблица и колонка, ключ в Redis. Стор внутри процесса пуст после рестарта и невидим
|
|
23
|
+
второй реплике: с двумя воркерами событие обработают оба.
|
|
24
|
+
3. **МЕХАНИЗМ**, и он ОБЯЗАН быть атомарным. «Прочитать, потом записать» — не исключение: две попытки
|
|
25
|
+
приходят ОДНОВРЕМЕННО, обе не находят ключа, обе пишут. Такая дедупликация проходит однопоточный
|
|
26
|
+
тест и падает на настоящей двойной доставке. Атомарно — уникальный индекс, конфликт вставки
|
|
27
|
+
и есть ответ «уже обработано».
|
|
28
|
+
|
|
29
|
+
## Подпись проверяется ПЕРВОЙ
|
|
30
|
+
|
|
31
|
+
Адрес вебхука публичен: без подписи событие «оплата прошла» присылает кто угодно. Четыре свойства
|
|
32
|
+
ОБЯЗАТЕЛЬНЫ вместе: **до разбора тела** (иначе чужие данные уже прошли через логику) · **по СЫРЫМ
|
|
33
|
+
байтам** (разбор и сборка меняют байты, подпись не совпадёт никогда, и обычное «лечение» — выключить
|
|
34
|
+
проверку) · **сравнением постоянного времени** (обычное выдаёт временем длину угаданного префикса) ·
|
|
35
|
+
**в окне свежести** (иначе перехваченный запрос годен вечно).
|
|
36
|
+
|
|
37
|
+
## Порядок доставки не гарантирован — и это ТА ЖЕ причина
|
|
38
|
+
|
|
39
|
+
Попытки доставки независимы: случившееся раньше событие приезжает позже и перезаписывает более
|
|
40
|
+
новое состояние — снова тихо и снова про деньги.
|
|
41
|
+
|
|
42
|
+
**Это часть правила, а не отдельная запись бэклога.** Дубль и перестановка — одно следствие ретрая.
|
|
43
|
+
Правило, закрывающее дубли и молчащее о порядке, выдаёт ЛОЖНОЕ закрытие класса: читатель чинит
|
|
44
|
+
дедупликацию, считает ретрай разобранным и пишет обработчик, присваивающий состояние вслепую.
|
|
45
|
+
Лечатся обе половины в одном месте: цена совместного изложения — строка контракта, цена разделения —
|
|
46
|
+
та половина, которая не спасает.
|
|
47
|
+
|
|
48
|
+
Ответов два: применять событие, только если его версия новее применённой, либо сделать обработчик
|
|
49
|
+
перестановочным. «Порядок гарантирует отправитель» фактически неверно и потому доказанный дефект,
|
|
50
|
+
а не выбор.
|
|
51
|
+
|
|
52
|
+
## Три класса отказа
|
|
53
|
+
|
|
54
|
+
Набор ЗАКРЫТЫЙ и ОБЯЗАТЕЛЬНЫЙ: обработчик с подписью, но с двойным начислением, теряет столько же,
|
|
55
|
+
сколько тот, что подпись не проверял.
|
|
56
|
+
|
|
57
|
+
| Класс | Признак у владельца | Лечение |
|
|
58
|
+
|---|---|---|
|
|
59
|
+
| `подделка` | в базе события, которых нет в кабинете отправителя | подпись первой, по сырому телу, постоянным сравнением |
|
|
60
|
+
| `повтор` | в отчёте партнёра ДВЕ комиссии за один платёж | ключ из события, общий стор, атомарное исключение |
|
|
61
|
+
| `перестановка` | статус подписки «откатился» сам, без действий клиента | применять событие новее применённого |
|
|
62
|
+
|
|
63
|
+
## Артефакт и ворота
|
|
64
|
+
|
|
65
|
+
`docs/webhook-contract.md` — объявление плюс квитанция: по строке на класс, и в каждой
|
|
66
|
+
ДОКАЗАТЕЛЬСТВО — файл теста, доставляющего ОДНО событие ДВАЖДЫ и утверждающего ОДНО начисление.
|
|
67
|
+
Форма полей и закрытые списки — в шапке проверки:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node .claude/hooks/check-webhook-contract.cjs .
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`0` классы закрыты и решения названы · `1` дефект ДОКАЗАН и назван · `2` **проверка НЕ ВЫПОЛНЕНА**
|
|
74
|
+
(нет контракта, нераспознанное значение, законные «вебхуков нет» и «НЕ ВЫПОЛНЕНА с причиной»).
|
|
75
|
+
Код `2` никогда не значит «всё в порядке».
|
|
76
|
+
|
|
77
|
+
## Честная разметка слоя
|
|
78
|
+
|
|
79
|
+
Слои — по [`cost-of-detection-ladder`](./cost-of-detection-ladder.md).
|
|
80
|
+
|
|
81
|
+
**Слой 1 (детерминированно):** ключ, источник, стор и механизм названы и не из форм, которые
|
|
82
|
+
заведомо не работают; подпись объявлена со всеми четырьмя свойствами; порядок имеет ответ;
|
|
83
|
+
классы закрыты, и файл теста в каждой строке СУЩЕСТВУЕТ. Это проверка ДЕКЛАРАЦИИ.
|
|
84
|
+
|
|
85
|
+
**Слой 3–4 (остаётся суждением, и сузить нечем):** доставляет ли названный тест то же событие дважды
|
|
86
|
+
и утверждает ли ОДНО начисление; есть ли уникальный индекс в развёрнутой схеме. Детерминированной
|
|
87
|
+
половины здесь быть НЕ МОЖЕТ по названной причине: у пакета ноль зависимостей, он не запускает тесты
|
|
88
|
+
проекта, не ходит в базу и не разбирает исходники языков продукта.
|
|
89
|
+
|
|
90
|
+
**Нового семейства идентификаторов НЕТ, и это решение.** `FR-LOOK-nnn` отвечает «снятое с источника
|
|
91
|
+
доехало до спецификации?»; здесь снимать нечего — обязательство рождается из ТОПОЛОГИИ ДОСТАВКИ
|
|
92
|
+
отправителя, свойства протокола, а не наблюдаемой черты чужого продукта.
|
|
93
|
+
|
|
94
|
+
## Самопроверка
|
|
95
|
+
|
|
96
|
+
1. Назови ПОЛЕ ключа повторности и МЕСТО его хранения. Не можешь назвать оба — ключа нет.
|
|
97
|
+
2. Что будет, если две попытки придут одновременно? «Одна увидит запись первой» — это оба начисления.
|
|
98
|
+
3. Подпись проверяется до первого разбора тела? Что сделает обработчик с событием, приехавшим после
|
|
99
|
+
более нового? «Такого не бывает» — не ответ.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Insights Capture Rules
|
|
2
2
|
|
|
3
3
|
When and how to record development "грабли" (rakes) into the project knowledge
|
|
4
|
-
base. Used by `/myinsights` and the `SessionStart` hook
|
|
4
|
+
base. Used by `/myinsights` and the `SessionStart`/`UserPromptSubmit` hook
|
|
5
5
|
(`.claude/hooks/session-insights.cjs`).
|
|
6
6
|
|
|
7
7
|
## When to Capture
|
|
@@ -53,11 +53,16 @@ Use lowercase, hyphenated, specific tags:
|
|
|
53
53
|
|
|
54
54
|
Aim for 2-5 tags per entry.
|
|
55
55
|
|
|
56
|
-
##
|
|
56
|
+
## Prompt-Time Injection on UserPromptSubmit
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
Markdown at `.claude/insights/index.md` remains the source of truth. After it is
|
|
59
|
+
established, capture makes a best-effort idempotent `dz teach` duplicate; optional dz
|
|
60
|
+
failure cannot undo the file write.
|
|
61
|
+
|
|
62
|
+
On `UserPromptSubmit`, a successful non-empty `dz recall` result from the insight
|
|
63
|
+
domain is the only state that suppresses local output. Absent, failing, or empty recall
|
|
64
|
+
uses the local fallback of the three most recent Markdown entries, never both sources.
|
|
65
|
+
`SessionStart` retains only the missing-carrier hint.
|
|
61
66
|
|
|
62
67
|
## Storage Lifecycle
|
|
63
68
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Долгая задача: минуты, а не секунды
|
|
2
|
+
|
|
3
|
+
Действует, когда одна операция работает МИНУТЫ: расшифровка и нарезка видео, генерация
|
|
4
|
+
изображения, большой отчёт. Обычный запрос-ответ через веб такое не выдерживает ПО ПОСТРОЕНИЮ.
|
|
5
|
+
|
|
6
|
+
## «Нет ответа» — это не «выполняется»
|
|
7
|
+
|
|
8
|
+
Молчат ОДИНАКОВО три разные вещи: живая задача, умерший исполнитель и оборванный посредник
|
|
9
|
+
(прокси, балансировщик, CDN, сам браузер — у каждого свой таймаут простоя). Различает их только
|
|
10
|
+
ЧТЕНИЕ состояния. Прочитать молчание как «выполняется» — значит стереть третье состояние, и из
|
|
11
|
+
этой одной подмены растут все три отказа ниже.
|
|
12
|
+
|
|
13
|
+
**Идентификатор ОБЯЗАН выдаваться ДО начала работы.** Не «сделайте асинхронно», а: назовите ПОЛЕ,
|
|
14
|
+
по которому клиент второй раз находит СВОЮ задачу (`job_id`), и покажите, где оно живёт — в каком
|
|
15
|
+
ответе выдаётся и по какому чтению возвращается. Ручка, приходящая вместе с результатом, умирает
|
|
16
|
+
вместе с оборванным ответом: работа выполнена, платёж списан, спросить больше нечем.
|
|
17
|
+
|
|
18
|
+
## Три отказа
|
|
19
|
+
|
|
20
|
+
| Отказ | Что видит клиент | Лечение |
|
|
21
|
+
|---|---|---|
|
|
22
|
+
| `разрыв` | ошибка при успешно потраченных деньгах: посредник закрыл соединение по таймауту простоя, работа при этом дошла до конца | вызов создания возвращает идентификатор СРАЗУ (202), результат — отдельным чтением. Синхронный ответ законен, только если потолок работы КОРОЧЕ самого короткого таймаута на пути |
|
|
23
|
+
| `повтор-заново` | счёт за внешние вызовы удваивается с каждой попыткой | **Повтор ОБЯЗАН ПРОДОЛЖАТЬ, а не начинать заново.** Механизм называется: идемпотентный ключ (тот же ключ → та же задача), запись в хранилище (строка задачи — источник истины), аренда исполнителя (второй воркер не заберёт занятое) |
|
|
24
|
+
| `третья-копия` | состояния не видно, пользователь жмёт кнопку ещё раз | показывать состояние по идентификатору и гасить кнопку, пока задача жива |
|
|
25
|
+
|
|
26
|
+
**Смежность с вебхуками названа, но не переписана.** Повторная доставка ВХОДЯЩИХ вебхуков — про то
|
|
27
|
+
же удвоение, и у неё СВОЁ соседнее правило в `.claude/rules/`. Механизм другой: там повтор приходит
|
|
28
|
+
ИЗВНЕ и вы им не управляете, здесь его порождает ваш же клиент, и потому лечится он на стороне
|
|
29
|
+
создания задачи.
|
|
30
|
+
|
|
31
|
+
## Три состояния, не два
|
|
32
|
+
|
|
33
|
+
Набор ЗАКРЫТЫЙ и ОБЯЗАТЕЛЬНЫЙ: `выполняется` · `успех` · `отказ`. Два состояния («идёт» и
|
|
34
|
+
«готово») — это и есть дефект: отказу негде появиться, и он показывается пользователю как вечный
|
|
35
|
+
прогресс. Каждое состояние обязано ВЫГЛЯДЕТЬ по-своему — прогресс «2 из 7», список готовых ссылок,
|
|
36
|
+
причина отказа с кнопкой «повторить». Два состояния, неразличимые на экране, — одно состояние.
|
|
37
|
+
|
|
38
|
+
## Артефакт и ворота
|
|
39
|
+
|
|
40
|
+
`docs/long-job-contract.md` — объявление плюс квитанция, по строке на каждое состояние, и в каждой
|
|
41
|
+
строке ДОКАЗАТЕЛЬСТВО, называющее идентификатор. Точная форма полей и закрытые списки значений — в
|
|
42
|
+
шапке проверки:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
node .claude/hooks/check-job-contract.cjs .
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`0` три состояния различимы и проверены по идентификатору · `1` дефект ДОКАЗАН и назван (молчание
|
|
49
|
+
объявлено «выполняется», идентификатор выдаётся после завершения, повтор начинает заново,
|
|
50
|
+
пропущенное состояние, два неразличимых состояния, след без идентификатора, синхронный ответ на
|
|
51
|
+
работу длиннее окна) · `2` **проверка НЕ ВЫПОЛНЕНА** (нет контракта, нераспознанное значение,
|
|
52
|
+
длительность без единицы, либо законные ответы «долгих задач нет» и «НЕ ВЫПОЛНЕНА с причиной»).
|
|
53
|
+
Код `2` никогда не значит «всё в порядке».
|
|
54
|
+
|
|
55
|
+
## Честная разметка слоя
|
|
56
|
+
|
|
57
|
+
Слои — по [`cost-of-detection-ladder`](./cost-of-detection-ladder.md).
|
|
58
|
+
|
|
59
|
+
**Слой 1 (детерминированно):** идентификатор назван именем поля и выдаётся до начала работы;
|
|
60
|
+
молчание объявлено «неизвестно»; три состояния названы, различимы и каждое со следом; повтор
|
|
61
|
+
продолжает по названному механизму; потолок работы сравнён с окном посредника. Это проверка
|
|
62
|
+
ДЕКЛАРАЦИИ.
|
|
63
|
+
|
|
64
|
+
**Слой 3–4 (остаётся суждением, и сузить нечем):** переживает ли сервер настоящий разрыв; тот ли
|
|
65
|
+
это потолок; действительно ли повторный запрос попадает в ту же задачу. Детерминированной половины
|
|
66
|
+
здесь быть НЕ МОЖЕТ по названной причине: у пакета ноль зависимостей, нет ни исполнителя, ни
|
|
67
|
+
посредника, а вердикт выносит только прогон с оборванным соединением и повтором.
|
|
68
|
+
|
|
69
|
+
## Самопроверка
|
|
70
|
+
|
|
71
|
+
1. Назови поле идентификатора. Клиент получает его ДО начала работы или вместе с результатом?
|
|
72
|
+
2. Что показывается пользователю в каждом из трёх состояний — три разных экрана или два?
|
|
73
|
+
3. Второй одинаковый запрос попадёт в ту же задачу или начнёт вторую? Чем это обеспечено?
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Стоимость внешних вызовов модели: счёт выставляют чужие действия
|
|
2
|
+
|
|
3
|
+
Действует, когда продукт зовёт наружу платную модель — распознавание фото, расшифровку записи,
|
|
4
|
+
векторизацию базы. Цена берётся за ВЫЗОВ, а вызов чаще запускает посетитель, а не разработчик.
|
|
5
|
+
|
|
6
|
+
**Отказ, который НЕЛЬЗЯ ОТКАТИТЬ, и этим класс отличается от всех прочих.** Открытый порт закрывают,
|
|
7
|
+
виджет перепроверяют — история кончается. Здесь один незакрытый цикл или один злонамеренный
|
|
8
|
+
посетитель даёт СЧЁТ: деньги ушли, и никакая правка кода их не вернёт. Поэтому вопрос не «заметим
|
|
9
|
+
ли», а «что отказало ДО вызова».
|
|
10
|
+
|
|
11
|
+
## Правило №0 — несконфигурированный потолок ОТКАЗЫВАЕТ
|
|
12
|
+
|
|
13
|
+
**Инвариант:** предел, который не задан, ОБЯЗАН валить запуск, называя ненастроенный вызов. Пустая
|
|
14
|
+
переменная окружения не значит «ограничений нет».
|
|
15
|
+
|
|
16
|
+
Это [`honest-configuration`](./honest-configuration.md) CFG-S1, продолженный на деньги: отсутствующее
|
|
17
|
+
обязательное значение отказывает и называет внешнее последствие. Последствие здесь — счёт, поэтому
|
|
18
|
+
второго шанса нет и правило безусловное.
|
|
19
|
+
|
|
20
|
+
## Два источника расхода — набор ЗАКРЫТЫЙ, и они отказывают по-разному
|
|
21
|
+
|
|
22
|
+
| Источник | Кто крутит счётчик | Чем ограничивается |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `свой-код` | ваш цикл, повтор, пересчёт таблицы | вы разоряете СЕБЯ; границу ставит ваш же код, суточного потолка достаточно |
|
|
25
|
+
| `посторонний` | посетитель решает, сколько раз позвать | вас разоряет ДРУГОЙ; суточного потолка НЕДОСТАТОЧНО — один посетитель съедает дневной бюджет до обеда, поэтому предел НА ПОЛЬЗОВАТЕЛЯ обязан уметь связать |
|
|
26
|
+
|
|
27
|
+
Связать он умеет, только когда названо, **что такое один пользователь** для того, кто вошёл или не
|
|
28
|
+
вошёл. Предел «на аккаунт» для анонимного посетителя не связывает НИ ОДНОГО вызова: написан как
|
|
29
|
+
защита, ведёт себя как её отсутствие.
|
|
30
|
+
|
|
31
|
+
## Что обязано быть названо
|
|
32
|
+
|
|
33
|
+
1. **Предел ОБЯЗАН быть назван числом, а не намерением.** «Разумный», «по ситуации», пустая клетка —
|
|
34
|
+
это отсутствие предела: сравнить со счётчиком нечего. Чисел два — на пользователя и на сутки,
|
|
35
|
+
причём персональное НЕ БОЛЬШЕ суточного, иначе оно не сработает никогда.
|
|
36
|
+
2. **Достижение предела есть ОТКАЗ, а не тихая деградация.** Деградация — предел, о котором
|
|
37
|
+
пользователь не узнал, а вы узнаете из счёта: система зовёт модель «поменьше» и платит дальше.
|
|
38
|
+
Очередь тоже не годится: она переносит трату, а не отменяет.
|
|
39
|
+
3. **Названо место, где расход виден** — адрес, который можно открыть: панель, файл, команда. Предел,
|
|
40
|
+
о котором нельзя узнать до счёта, не предел.
|
|
41
|
+
4. **Счёт ведётся по ПОПЫТКАМ.** Провайдер берёт деньги за попытку: таймаут, отказ модели и повтор
|
|
42
|
+
оплачены как успех. Счётчик по успехам оставляет повторы вне предела — а повтор долгой фоновой
|
|
43
|
+
задачи удваивает счёт (правило этого пакета о долгих фоновых задачах; здесь только денежная
|
|
44
|
+
сторона, политика повторов — там).
|
|
45
|
+
|
|
46
|
+
## Артефакт и ворота
|
|
47
|
+
|
|
48
|
+
`docs/model-cost-contract.md` — объявление плюс строка на каждый вызов: кто запускает, нужен ли вход,
|
|
49
|
+
единица счёта, два предела, поведение при достижении. Точная форма полей и закрытые списки значений —
|
|
50
|
+
в шапке проверки:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
node .claude/hooks/check-model-cost.cjs .
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`0` каждый вызов назван и каждый предел — число, способное связать · `1` дефект ДОКАЗАН и назван
|
|
57
|
+
(намерение или бесконечность вместо числа, персональный предел выше суточного, деградация либо
|
|
58
|
+
очередь вместо отказа, «без ограничений» при ненастроенном потолке, счёт по успехам, расход без
|
|
59
|
+
адреса, посторонний вызов без единицы счёта или с несуществующей для него) · `2` **проверка НЕ
|
|
60
|
+
ВЫПОЛНЕНА** (нет контракта, нераспознанное значение, повтор строк, либо законные ответы «внешних
|
|
61
|
+
вызовов модели нет» и «НЕ ВЫПОЛНЕНА с причиной»). Код `2` никогда не значит «всё в порядке».
|
|
62
|
+
|
|
63
|
+
## Честная разметка слоя
|
|
64
|
+
|
|
65
|
+
Слои — по [`cost-of-detection-ladder`](./cost-of-detection-ladder.md).
|
|
66
|
+
|
|
67
|
+
**Слой 1 (детерминированно):** пределы — положительные числа; персональный не выше суточного; у
|
|
68
|
+
постороннего вызова единица счёта названа и существует для него; при достижении отказ; ненастроенный
|
|
69
|
+
потолок валит запуск; у расхода есть адрес. Это проверка ДЕКЛАРАЦИИ.
|
|
70
|
+
|
|
71
|
+
**Слой 3–4 (остаётся суждением, и сузить нечем):** применяет ли код объявленное число; растёт
|
|
72
|
+
счётчик до вызова или после; та ли цена у провайдера сегодня. Детерминированной половины здесь быть
|
|
73
|
+
НЕ МОЖЕТ по названной причине: у пакета ноль зависимостей, он не исполняет ваш код и не видит
|
|
74
|
+
биллинга, а применение подтверждает только прогон, упершийся в предел, и счёт после него.
|
|
75
|
+
|
|
76
|
+
**Таблицы-семени НЕТ, и это решение.** `FR-LOOK-nnn` отвечает «снятое СНАРУЖИ доехало до
|
|
77
|
+
спецификации?»; здесь снаружи ничего не снимали. Перенос числа из одного нашего документа в другой
|
|
78
|
+
доказал бы, что мы умеем копировать свой текст, — про деньги он не говорит ничего.
|
|
79
|
+
|
|
80
|
+
## Самопроверка
|
|
81
|
+
|
|
82
|
+
1. Назови число предела на одного пользователя. Назвал прилагательное — предела нет.
|
|
83
|
+
2. Вызов может запустить посторонний? Тогда что такое «один» для него, пока он не вошёл?
|
|
84
|
+
3. При достижении предела — отказ или продолжение подешевле?
|
|
85
|
+
4. Куда посмотришь, чтобы увидеть сегодняшний расход раньше счёта?
|