Repository navigation
Commit 169ff7e
feat(cli): os validate --target judges the stack against a target environment's restriction profile (#31237)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiLhXvGV9xRtyZvH3Pjto2JA)_
Fixes #31156
Clause-②: yes
Metadata-protocol: none
## 维护者速读
- 改了什么:`os validate` 新增 `--target PROFILE_FILE`。它读取目标环境的限制配置(第 1 项已合入的
`RestrictionProfileSchema`),把本地元数据里该环境不允许的东西逐条拒掉,每条写明是哪一项、被哪条规则拒(写代码、定时任务、流程节点类型);本地
spec / skills 版本和目标环境不一致时只告警,不拒。
- 为什么改:云端与本地唯一的区别就是这份限制配置(cloud#3659 裁决
A)。开发者在部署前就能知道"这份元数据在目标环境会被拒",而不是部署后才发现。
- 风险与回滚:不带 `--target` 时命令行为完全不变(有对照测试)。只改 `packages/cli`,patch
版本。回滚就是还原这个 PR。另外:`--target` 目前只接受文件,传 URL 会被明确拒绝,因为目标环境还没有定义发布限制配置的地址(第
3 项在 cloud 仓)。
- 先看:`packages/cli/src/utils/restriction-profile.ts`(三条读者规则在
`judgeNodeType`);`packages/cli/src/commands/validate.ts` 的 step 0 / 1c /
3f;`packages/cli/test/validate-target-restriction-profile.test.ts`。
- 两分钟验证:`pnpm --filter @objectstack/cli exec vitest run
src/utils/restriction-profile.test.ts
test/validate-target-restriction-profile.test.ts`,预期 30 个用例全部通过。
- 需要你做的:无,席位落地。
## What changes
`os validate --target PROFILE_FILE` judges the stack against a target
environment's restriction profile. Without `--target` nothing new runs.
**Step 0, the profile.** `loadRestrictionProfile` reads the file and
parses it with `RestrictionProfileSchema` (`@objectstack/spec/system`,
PR #31191). It runs before the stack is loaded. A URL, a missing file, a
file that is not JSON, or a profile the schema refuses is refused
through the catch-all (`--json`: `error`, exit 1), and the message names
the schema issue path. `{}` is refused, so an absent dimension never
reads as "no restriction".
**Step 1c, version drift.** `restrictionProfileVersionDrift` compares
the project's `@objectstack/spec` and `@objectstack/skills` with
`versions.spec` / `versions.skills`. They are resolved from the config's
directory; spec falls back to the CLI's own copy, the way the protocol
drift advisory resolves it. Each package that differs gives one warning
(`package`, `local`, `target`, `message`). A project that does not
install the skills catalog gets `local: null`. The warnings ride
`--json` `warnings` (appended last in `warningsSoFar()`), and the text
face prints them in their own block. They never refuse, and `--strict`
does not gate them: the target's version is the environment's, not
something the stack can fix, the same reason the protocol drift advisory
is never gated by `--strict`.
**Step 3f, the refusals.** `judgeRestrictionProfile` runs after every
other refusal. Those gates ask whether any runtime accepts the stack;
this one asks whether this target does. It gives one row per refused
item: `rule` (the ONE profile dimension), `where` (the item, named),
`path` and `message`. It walks each `packages[]` entry, or the stack and
its `manifest` when there are no packages, the way the CLI's other walks
do.
- `allowCode: false` refuses each `hooks` entry (the profile docblock
names hooks) and each item under a key the spec classifies as code:
`UNMANAGED_INSTALL_KEY_DISPOSITIONS` disposition `code`, the one table
`defineStack`'s unmanaged-install refusal and the install planner
already share (`functions` per function, `plugins` / `devPlugins` per
entry, the others per key, on the stack or its manifest). An empty key
(`functions: {}`) refuses nothing, by the same
`unmanagedInstallValueCarries` test.
- `allowScheduledAutomation: false` refuses each flow of `type:
'schedule'` and each `jobs` entry.
- `allowedFlowNodeTypes` refuses each node, at every region depth
(`collectFlowGraphs`), whose built-in type is not listed.
Reader rules, from the contract review of item 1 (record 6710350208 ③/④,
restated in the claim):
1. `start` / `end` (`FLOW_STRUCTURAL_NODE_TYPES`) are always allowed.
2. A node type outside `FlowNodeAction` (`FLOW_BUILTIN_NODE_TYPES`)
cannot be named by `allowedFlowNodeTypes`, so `allowCode` governs it.
Under `allowCode: false` it is refused naming that rule. Under `true` it
is allowed.
3. A `script` node under `allowCode: false` is refused by `allowCode`
alone, whether or not `allowedFlowNodeTypes` lists it. Under `true` it
is judged by `allowedFlowNodeTypes`.
**URL form.** No route for an environment's published profile is defined
in this repository: no `restriction` route appears in the spec route
ledger or under `packages/spec/src/api`. Card 3
(objectstack-ai/cloud#3713) is in the cloud repository, which this
session cannot read. So the URL form is not guessed. A URL is refused,
and the message names the file form as the remedy. Once card 3 publishes
an endpoint, the URL form is a follow-up.
The gate-parity ledger (`test/validate-build-gate-parity.test.ts`) gains
`judgeRestrictionProfile` under `VALIDATE_ONLY_GATES` (`os build` names
no target, so there is no profile for it to judge against), and the
other two calls under a `NOT_A_GATE` reason.
## Tests
- `src/utils/restriction-profile.test.ts` (21): a CONTROL all-open
profile; each dimension (allowCode over a hook, the functions, a plugin,
a script node and a plugin node; an empty code key; a manifest code key;
allowScheduledAutomation over a job and a scheduled flow;
allowedFlowNodeTypes at the top level and inside a loop body); the
`packages[]` walk; the three reader rules (start/end under an empty
list; `approval` under allowCode false and true; `script` naming one
rule, and judged by the list under allowCode true); the loader (valid,
invalid naming the issue path, `{}`, not JSON, missing, URL); version
drift (CONTROL equal, one package differing, skills not installed).
- `test/validate-target-restriction-profile.test.ts` (9, in-process
`Validate.run`): CONTROL without `--target` (exit 0, no drift entry);
CONTROL all-open profile; one refusal per dimension through `--json`;
the text face; version drift as a warning (exit 0, and `--strict` with
the drifted target reads the same exit as `--strict` with no target); an
invalid profile; a URL.
<details><summary>Ablations (one-time, scripts/ablation-replace.mjs,
each restored to blob 11f11e5 == HEAD with git diff HEAD empty; the
tests import src, so no dist is on the path)</summary>
- Reader rule 3 disabled (`type === 'script'` → `'ABLATED_script'`): 3
failed / 27 passed (the command's allowCode row set, the reader's
allowCode row set, "script names ONE rule").
- Scheduled flow check disabled (`flow.type === 'schedule'` →
`'ABLATED_schedule'`): 3 failed / 27 passed (the command's
allowScheduledAutomation row, its text face, the reader's schedule row
set).
- Reader rule 1 deleted (the `FLOW_STRUCTURAL_NODE_TYPES` early return):
5 failed / 25 passed (every allowedFlowNodeTypes case now also refuses
`start` / `end`).
</details>
## Acceptance notes
- **Engine built-ins outside `FlowNodeAction`.** `parallel` and
`try_catch` (ADR-0031 containers) are registered by
`@objectstack/service-automation`'s own builtins
(`src/builtin/parallel-node.ts`, `try-catch-node.ts`), but they are not
`FlowNodeAction` ids. Reader rule 2, as ruled, therefore refuses them
under `allowCode: false` (the message says only that the type is outside
the registry, not that it is a plugin), and no profile can list them.
This is fail-closed, not silent. Raised as an open question for the spec
lane in the report.
- `devPlugins` is refused under `allowCode: false` because the spec's
code table classifies it `code`, although it is a dev-only overlay. Kept
literal to the one table rather than carving out a second list. Noted in
the report.
- `allowCode` reads exactly what the profile docblock and the code table
name. JS bodies elsewhere (a job's or an action's sandboxed `body`) are
not judged by `allowCode`: a job is still refused by
`allowScheduledAutomation`, and an action's `target` function is refused
through `functions`. Noted, not filed.
- The optional one-sentence `allowedFlowNodeTypes` docblock note in
`packages/spec` (review 6710350208 ④) is not ridden here: the claim
keeps `packages/spec` out. Listed as an open question in the report.
- In the vitest environment `pnpm exec` sets `NODE_PATH` to the
workspace's hoisted store, so a tmp-dir fixture resolves the workspace
`@objectstack/skills`. The command test therefore installs its own
skills manifest at a known version, and the not-installed case is pinned
on the reader with a tree whose resolution is controlled.
## Verification
The derived gate list below was taken on dcbb8da, based on `66bf5b4a`.
`origin/main` then moved to `df22adc37` and was merged once before
opening (head 8c5e74a). The incoming commits touch `packages/spec`
(docs, migration entries) and one unrelated `packages/cli` test, so the
scoped re-checks ran on 8c5e74a: `pnpm --filter @objectstack/spec build
&& pnpm --filter @objectstack/spec check:generated` exit 0 (all 14
artifacts up to date); the two new test files plus
`test/validate-build-gate-parity.test.ts`, Test Files 3 passed, Tests 63
passed; `pnpm --filter @objectstack/cli typecheck` exit 0. The branch
delta against `origin/main` is still exactly the six files of this PR.
Before the merge, 651989c added only a one-line changeset wording
change (an angle-bracket placeholder spelled as a path), and the
families that read the changeset were rerun on it: `node
scripts/check-adr-0087-registration.mjs --base origin/main`, `node
scripts/check-changeset-no-major.mjs --base origin/main`, `node
scripts/check-empty-changeset.mjs --base origin/main`, `pnpm
check:changeset-gate-self-tests`, `pnpm check:doc-authoring`, `pnpm
check:issue-citations`, `pnpm check:nul-bytes` and `node
scripts/check-closing-keyword-parity.mjs`, each exit 0.
<details><summary>Derived gates (node scripts/pm/dispatch-gates.mjs
--commands, 62 commands)</summary>
All on dcbb8da:
- `node scripts/check-adr-0087-registration.mjs --base origin/main ::
exit 0`
- `node scripts/check-adr-0087-registration.mjs --self-test :: exit 0`
- `node scripts/check-changeset-no-major.mjs --base origin/main :: exit
0`
- `node scripts/check-closing-keyword-parity.mjs :: exit 0`
- `node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0`
- `node scripts/check-comment-mask-adoption.mjs :: exit 0`
- `node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0`
- `node scripts/check-comment-mask-corpus.mjs :: exit 0`
- `node scripts/check-dts-emitted.mjs --self-test :: exit 0`
- `node scripts/check-empty-changeset.mjs --base origin/main :: exit 0`
- `node scripts/check-empty-changeset.mjs --self-test :: exit 0`
- `node scripts/check-issue-citations.mjs :: exit 0`
- `node scripts/check-plugin-teardown-shape.mjs :: exit 0`
- `node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0`
- `node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0`
- `node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0`
- `node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0`
- `node scripts/ci/snapshot-next.mjs --self-test :: exit 0`
- `node scripts/docs-audit/check-affected-docs.mjs :: exit 0`
- `node scripts/docs-audit/check-drift-comment.mjs :: exit 0`
- `node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0`
- `node scripts/release-pending-publish.mjs --self-test :: exit 0`
- `pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit
0`
- `pnpm check:changeset-gate-self-tests :: exit 0`
- `pnpm check:ci-filter-parity :: exit 0`
- `pnpm check:cli-test-child-env :: exit 0`
- `pnpm check:closed-ceremony-text :: exit 0`
- `pnpm check:cross-package-test-inputs :: exit 0`
- `pnpm check:doc-authoring :: exit 0`
- `pnpm check:driver-memory-census :: exit 0`
- `pnpm check:dts-closure :: exit 0`
- `pnpm check:dual-build-cjs-loads :: exit 0`
- `pnpm check:engine-double-contract :: exit 0`
- `pnpm check:gitlink-declared :: exit 0`
- `pnpm check:i18n :: exit 0`
- `pnpm check:i18n-coverage :: exit 0`
- `pnpm check:i18n-walk-parity :: exit 0`
- `pnpm check:issue-citations :: exit 0`
- `pnpm check:keyed-text-bounds :: exit 0`
- `pnpm check:lean-entry-closure :: exit 0`
- `pnpm check:logger-receiver-detach :: exit 0`
- `pnpm check:nul-bytes :: exit 0`
- `pnpm check:objectql-double-limit :: exit 0`
- `pnpm check:org-identifier :: exit 0`
- `pnpm check:page-declaration-shape :: exit 0`
- `pnpm check:platform-object-tenancy-census :: exit 0`
- `pnpm check:published-files :: exit 0`
- `pnpm check:query-options-erasure :: exit 0`
- `pnpm check:refd-timer-probe :: exit 0`
- `pnpm check:registry-log-declared :: exit 0`
- `pnpm check:rest-log-spy-declared :: exit 0`
- `pnpm check:slot-lookup :: exit 0`
- `pnpm check:sourcemap-no-sources-content :: exit 0`
- `pnpm check:system-context-census :: exit 0`
- `pnpm check:test-source-alias :: exit 0`
- `pnpm check:tier-file-adoption :: exit 0`
- `pnpm check:type-check-coverage :: exit 0`
- `pnpm check:type-check-debt :: exit 0`
- `pnpm check:undeclared-dep-imports :: exit 0`
- `pnpm check:vouch-for-reference-callers :: exit 0`
- `pnpm check:watch-hint-literal :: exit 0`
- `pnpm check:where-matcher :: exit 0`
On the first pass (head 3f6ecdb), six were not green and were fixed or
rerun:
- `node scripts/check-plugin-teardown-shape.mjs --self-test` exit 3: the
shallow clone did not hold the positive-control commit 621a487. It was
fetched, then exit 0.
- `pnpm check:dual-build-cjs-loads`, `pnpm check:i18n`, `pnpm
check:i18n-coverage`, `pnpm check:i18n-walk-parity` exit 3: their
prerequisites were not built. `turbo run build --filter='./packages/**'
--filter='./examples/**'` ran (82/82 tasks), then each exited 0.
- `pnpm check:test-source-alias` exit 1: the new command test reached
`@objectstack/objectql/core` and `@objectstack/plugin-security` through
the source's dynamic imports. Module-top imports were added (dcbb8da),
then exit 0.
Reconciliation: `node scripts/pm/dispatch-gates.mjs --ran` with the
exit-coded record gives 62 derived, 62 run, 0 NOT-MEASURED (a derived
zero: all 62 recorded an exit code).
Package, on dcbb8da:
- `pnpm --filter @objectstack/cli typecheck` exit 0 (tsc +
check:test-typecheck);
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2` VERDICT command-exit 0: Test Files 400 passed (400),
Tests 5653 passed (5653). The first full run (3f6ecdb) found
`validate-build-gate-parity.test.ts` "every call site … is classified"
red on the three new names. They were classified in dcbb8da. Its two
`published-subpath-*` suite errors were "packages/cli is not built" and
cleared after the build;
- `node --stack-size=4000 node_modules/eslint/bin/eslint.js packages/cli
--no-inline-config` exit 0;
- `node scripts/check-nul-bytes.mjs` exit 0.
The integration tier (`--project integration`) is declared to CI: the
diff touches no spawn entry and no integration-tier file.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(`packages/spec` not touched.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (eslint over
packages/cli, `--no-inline-config`, exit 0.)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
(`@objectstack/cli` patch; `Metadata-protocol: none` grades patch.)
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ihL2fKNXpyrradyNkX9jx)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent a0b40f2 commit 169ff7e
6 files changed
Lines changed: 929 additions & 0 deletions
File tree
- .changeset
- packages/cli
- src
- commands
- utils
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
91 | 91 | | |
92 | 92 | | |
93 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
94 | 103 | | |
95 | 104 | | |
96 | 105 | | |
| |||
103 | 112 | | |
104 | 113 | | |
105 | 114 | | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
106 | 120 | | |
107 | 121 | | |
108 | 122 | | |
| |||
206 | 220 | | |
207 | 221 | | |
208 | 222 | | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
209 | 230 | | |
210 | 231 | | |
211 | 232 | | |
| |||
228 | 249 | | |
229 | 250 | | |
230 | 251 | | |
| 252 | + | |
| 253 | + | |
231 | 254 | | |
232 | 255 | | |
233 | 256 | | |
| |||
264 | 287 | | |
265 | 288 | | |
266 | 289 | | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
| 297 | + | |
| 298 | + | |
| 299 | + | |
| 300 | + | |
| 301 | + | |
| 302 | + | |
267 | 303 | | |
268 | 304 | | |
269 | 305 | | |
| |||
291 | 327 | | |
292 | 328 | | |
293 | 329 | | |
| 330 | + | |
| 331 | + | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
294 | 342 | | |
295 | 343 | | |
296 | 344 | | |
| |||
1054 | 1102 | | |
1055 | 1103 | | |
1056 | 1104 | | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
| 1109 | + | |
| 1110 | + | |
| 1111 | + | |
| 1112 | + | |
| 1113 | + | |
| 1114 | + | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
| 1124 | + | |
| 1125 | + | |
| 1126 | + | |
| 1127 | + | |
| 1128 | + | |
| 1129 | + | |
| 1130 | + | |
| 1131 | + | |
| 1132 | + | |
| 1133 | + | |
| 1134 | + | |
| 1135 | + | |
1057 | 1136 | | |
1058 | 1137 | | |
1059 | 1138 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 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 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
0 commit comments