@protoboardai/ext-release 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,13 @@
1
+ Protoboard Stack: @protoboardai/ext-release
2
+ Copyright 2026 Delta Robotics, Inc.
3
+
4
+ This product includes software developed at Delta Robotics, Inc.
5
+ (https://deltaroboticsinc.com).
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License").
8
+ See the LICENSE file in this package for full terms.
9
+
10
+ "Protoboard", the Protoboard logo and the "0" mark are trademarks of
11
+ Delta Robotics, Inc. The License grants no rights to them (section 6);
12
+ see TRADEMARKS.md in the Protoboard Stack repository
13
+ (https://github.com/Delta-Robotics-Inc/protoboard-stack).
package/README.md CHANGED
@@ -1,3 +1,291 @@
1
- # Temporary Holding Version
1
+ # @protoboardai/ext-release
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ `protoboard release` and the Manufacturing Package
4
+ ([releases, package, SOPs and evals](../../docs/specs/release-package-and-evals.md)
5
+ §1–§3). A release refuses a project that is not clean, then
6
+ writes everything needed to order, make, assemble and check the product to
7
+ `release/<version>/`, laid out by unit, with a manifest that a rebuild in a
8
+ clean environment reproduces byte for byte. It never orders, uploads or
9
+ prints anything, and never calls a service.
10
+
11
+ | Kind | Id | What |
12
+ | --- | --- | --- |
13
+ | Operation | `release` (export; tool `chrome`) | The package; inputs `version` (default: the root module's `version`), `allow-dirty`, `check` (preconditions only). `protoboard release [--version <v>] [--allow-dirty] [--check]` runs it. |
14
+ | Operations | `package:spec`, `package:bom`, `package:work-order`, `package:assembly`, `package:test-plan` (pure) | The release's own section content, from the model and the collected files: identity and interfaces with ratings; module breakdown and ordering list; ordering checklist, fabrication orders, operations and kitting; plain assembly steps when nothing richer is installed; the basic test plan |
15
+ | Skill | `release` | The SOP: choose units, clear the preconditions, release, inspect |
16
+ | Tool | `chrome` | Headless Chrome for the unit figures and the PDFs (@protoboardai/ext-docs' renderer) |
17
+
18
+ ```ts
19
+ // protoboard.config.ts (or the default export of release.config.ts)
20
+ extensions: [..., "@protoboardai/ext-release"],
21
+ release: {
22
+ units: ["stack", "quadcopter-5in-arm"], // instance paths or module ids; the root is always a unit
23
+ build: 1, // products to build: ordering quantities × build
24
+ spares: { fastener: { percent: 20, min: 4 } }, // by part id, tag or category; overrides the defaults
25
+ },
26
+ ```
27
+
28
+ ## The package
29
+
30
+ ```
31
+ release/<version>/
32
+ 00-overview.pdf the cover, then the product page (what it is, features, functions, requirements with how
33
+ each is shown, from docs/product.json), units, order of work, index of files, traceability appendix
34
+ index.html all of it, navigable by unit and by section (assets/ holds its figures and Kode Mono)
35
+ manifest.json protoboard.release/v0
36
+ system/ the root unit: final assembly
37
+ spec-sheet.pdf bom.pdf bom.csv ordering-list.csv work-order.pdf assembly-guide.pdf test-plan.pdf
38
+ files/model/uhd-bundle.json the released model (artifact bases relative to the project)
39
+ files/firmware/… configurations other extensions contribute (ext-drone: betaflight-cli.txt)
40
+ units/<unit>/ one per release.units entry, same layout (no ordering list)
41
+ files/boards/<module>/ circuit: gerber/ (layers and drill), bom.csv, pnp.csv, board.step, board.glb
42
+ files/mechanical/<module>/ cad: <module>.step and the process exports (<module>.stl, .3mf, .dxf)
43
+ files/harnesses/<module>/ cad on a cable: routed <module>.step and .glb; cut-list.csv from the model
44
+ ```
45
+
46
+ **Units.** An instance path makes that instance a unit (folder: the path with
47
+ `/` as `.`); a module id makes every instance of the module one unit, built
48
+ once per instance. Every other instance folds into the nearest unit above it.
49
+ A unit inside another is built first; the root (`system/`, final assembly) is
50
+ last. A unit named by path may not sit inside one build of a module unit that
51
+ is built several times (the builds would differ). Files of a custom module go
52
+ to every unit that builds it.
53
+
54
+ **Files** come only from the providers' standard outputs (`<module>/out/`),
55
+ never from native files, renamed as above. The cut list is derived from the
56
+ cable's UHD facts (`performance` kind cable, `cable_build`, the conductor
57
+ link between its ends); a cable the unit makes or cuts without a cad
58
+ provider gets `files/harnesses/<module>/cut-list.csv` too, and the work order
59
+ and test plan name a cut list only when the package holds it.
60
+
61
+ ## Preconditions
62
+
63
+ Nothing is written unless all of these hold (`--check` reports them):
64
+
65
+ | Code | Rule |
66
+ | --- | --- |
67
+ | `REL_CHECK_ERRORS` | `protoboard check` has no errors. |
68
+ | `REL_STALE` | No provider finding ends in `_STALE` (warning or error). Every bound module has a recorded `<capability>:export` made with the current config file (digest) and provider, and, when the record has it, the current module (`capability.moduleDigest`, recorded by core since Stack 0.1). `out/` holds exactly the files that record lists, with its digests. Every file any operation record wrote is still the bytes its latest writer wrote (earlier packages under `release/` excepted: a release replaces its folder). |
69
+ | `REL_FRESHNESS_UNKNOWN` | No provider check was skipped: a skipped check cannot show its outputs are current. |
70
+ | `REL_NOT_EXPORTED` | Every bound module has standard outputs. |
71
+ | `REL_DIRTY` | In a git work tree, no uncommitted changes outside `release/` and `operations/` (`--allow-dirty` records them in the manifest instead). |
72
+ | `REL_UNIT_INVALID`, `REL_CONFIG_INVALID`, `REL_NO_VERSION` | The configuration resolves. |
73
+ | any error a section returns | e.g. ext-drone's `DRONE_CLI_STALE` when the flight-controller configuration no longer matches the model. |
74
+
75
+ ## Section contract
76
+
77
+ An extension contributes to a section by declaring an operation
78
+ `package:<section>` (`spec`, `bom`, `work-order`, `assembly`, `test-plan`;
79
+ effect `pure`). The release calls it once per unit, the others in
80
+ configuration order and the release's own entry last (so its work order can
81
+ number what they contribute), and renders what it returns into that unit's
82
+ PDF and `index.html`, the release's own content first.
83
+
84
+ ```ts
85
+ import type { SectionFn } from "@protoboardai/ext-release";
86
+
87
+ export const workOrder: SectionFn = ({ project, unit, units, release, files }) => {
88
+ if (unit.kind !== "root") return undefined; // nothing for this unit
89
+ return {
90
+ title: "Flight controller configuration", // its own numbered part of the document; omit to join the main body
91
+ order: 20,
92
+ blocks: [
93
+ { kind: "paragraph", text: "…" },
94
+ { kind: "table", columns: [{ key: "item", label: "Item" }], rows: [{ item: "…" }] },
95
+ ],
96
+ // work order only: operations the release numbers with its own (OP 10, 20, …)
97
+ operations: [{ stage: "configure", title: "Load the configuration", actions: ["…"], test: "…" }],
98
+ files: [{ path: "files/firmware/betaflight-cli.txt", from: "firmware/betaflight/cli.txt", role: "firmware-config" }],
99
+ diagnostics: [], // an error stops the release
100
+ };
101
+ };
102
+ ```
103
+
104
+ Blocks: `heading`, `paragraph` (`keep` holds it on the page of the next
105
+ block), `list`, `keyvalues`, `figures` (key figures), `table` (with `{ group
106
+ }` rows; `nowrap` columns), `note` (tip, caution, warning), `steps` (an id,
107
+ actions, parts with thumbnails and letters, a figure with callouts, a table,
108
+ tools, materials, torque, inspection, the test that follows, notes, trace),
109
+ `image` (with lettered `callouts`; `size: "small"`) and `kit` (a grid of
110
+ items with thumbnails). Files go under the unit folder (`files/…` for
111
+ manufacturing files; a unit-level data file such as `bom.csv`); a `from`
112
+ path is copied from the project, and the manifest traces it to the operation
113
+ record that wrote it. Types: `lib/types.ts`.
114
+
115
+ The context also carries:
116
+
117
+ - `figure(name, spec)`: renders an ext-docs `RenderSpec` (line art or
118
+ shaded, from served GLB paths, `project/<path>`) with the release's Chrome
119
+ into `assets/images/<name>.png` and returns its package path, size and
120
+ anchor positions (for callouts). A name is rendered once per release; the
121
+ same name with another spec is an error. Figures are rendered before the
122
+ documents, one Chrome at a time.
123
+ - `contributors`: the extensions contributing to this section, so a
124
+ baseline can step aside (the release's plain assembly steps give way to
125
+ ext-docs' illustrated ones).
126
+ - `operations`: for the release's own work order, the operations the others
127
+ returned. A `configure` operation (flash firmware, load a configuration)
128
+ runs after the assembly steps, between the power-up checks and the rest of
129
+ the bench test, and so before the steps that must follow the test.
130
+
131
+ `@protoboardai/ext-release/helpers` gives contributors what the release
132
+ knows: the custom modules a unit builds (`unitModules`, `providerModules`),
133
+ how many per build (`perBuild`), the files collected for one
134
+ (`moduleFiles`), its JSON standard outputs (`readOutput`), a GLB's size
135
+ (`glbBox`), the cut list and the bill of materials.
136
+ `@protoboardai/ext-release/testing` builds the units and contexts of a project
137
+ for a contributor's tests, recording figures instead of rendering them.
138
+
139
+ ## Bills of materials (PK2)
140
+
141
+ - **`bom.csv` / `bom.pdf`** per unit: one build's module tree (sub-assembly →
142
+ board → components), one row per instance with quantity per build and per
143
+ product, make/buy, manufacturer, MPN, tier and origin. A board's components
144
+ are its UHD children; the circuit provider's `out/bom.csv` is merged by its
145
+ `instance` column for value, package and MPN where the model has none.
146
+ - **`system/ordering-list.csv`**: every item to obtain for the whole product,
147
+ one line per part. Quantities come from the instance tree (independent of
148
+ the units). `needed = per product × build`; `spares = max(ceil(needed ×
149
+ percent / 100), min)` by the first matching rule: `release.spares[part id]`,
150
+ `release.spares[tag or category]`, then the defaults: mechanical hardware
151
+ (category `hardware` or tag `fastener`) 10 %, at least 2; components placed
152
+ on a board 5 %, at least 2; nothing else. `order` rounds `needed + spares`
153
+ up to whole packs (the offer's own, else the `pack_quantity` the model
154
+ states) and the chosen offer's minimum, and goes up to a later price break
155
+ where that costs less in all (`offer_flag` then says what the smaller
156
+ order would have cost). Parts that ship with another (`included_with`) are listed with
157
+ order 0; custom parts, boards and cables not included with a part are
158
+ `fabricate` lines.
159
+ - **Offers, offline only**: catalog offers vendored by `protoboard parts
160
+ offers` into `parts/offers.json` (`protoboard.offers/v0`), sourcing evidence
161
+ in `parts/evidence/<id>/sourcing.json` (@protoboardai/ext-sourcing: price
162
+ breaks, stock, lead time, lifecycle, alternates), and as a last resort a
163
+ supplier listing the model cites (a `usage_note` starting "Supplier
164
+ listing:"; where to buy, no price). Each catalog offer names the MPN it is
165
+ for, so a series passive's values are priced by their own offers. A value
166
+ with no offer for its own MPN stays unpriced; it is never priced by
167
+ another value's offer.
168
+ - **One quote, the bill of materials'** (docs/specs/bom.md §3.8). A
169
+ bought line is quoted by the model the bill of materials uses
170
+ (ext-sourcing `quoteHeldLines`), so a release and `protoboard bom` agree
171
+ on supplier and price for the same pieces: the offer a person chose for
172
+ the line in `parts/bom.json` (flagged "chosen in the bill of materials"),
173
+ else, in order, an offer with a price; not a full reel for fewer than a
174
+ reel (that line stays unpriced and says so in `offer_flag`, the reel
175
+ listed in `offer_alternatives`; a reel the quantity fills is quoted and
176
+ flagged); stock that covers the order or is not stated; a current price
177
+ before a stale one; the lowest cost of the order in dollars. Every price
178
+ is in US dollars: `unit_price`, `currency` (`USD`) and `extended`. An
179
+ offer in another currency is converted with the project's dated rate
180
+ table (`parts/fx-rates.json`, else the Stack's) and marked:
181
+ `supplier_unit_price`, `supplier_currency`, `fx_rate` and `fx_date` carry
182
+ the supplier's own price and the rate, `offer_flag` says "converted from
183
+ EUR at 1.1177 (2026-10-07)", and the PDF's price reads "(from EUR)". An
184
+ offer in a currency the table lacks is not quoted. The cost estimate is
185
+ the sum of the lines; its note names the currencies converted and the
186
+ table's date, and it says how many bought lines are priced. `currency` in
187
+ protoboard.config.ts no longer changes a release's quotes. The only
188
+ difference from the bill of materials is the quantity: a release adds
189
+ spares.
190
+
191
+ ## Work order, assembly guide and test plan (PK3–PK5)
192
+
193
+ The three documents follow one sequence: the unit's assembly plan from
194
+ `@protoboardai/ext-docs/plan` (the doc spec's steps, then automatic ones,
195
+ scoped to the links the unit makes; a board's nets are its copper, not
196
+ steps). The release's own content:
197
+
198
+ - **Work order.** Ordering checklist from the ordering list (the whole
199
+ product on the root, the unit's own items elsewhere), with supplier, lead
200
+ time and minimum order where an offer or sourcing record states them and
201
+ "unknown" otherwise. Fabrication orders, one line per made part, pointing
202
+ at the provider's details below. Operations, numbered OP 10, 20, …: kit and
203
+ incoming inspection (first-article ids), board assembly, cutting and
204
+ preparing cables whose installed length is shorter than supplied, one
205
+ operation per assembly step (parts, tools, materials, torque, an inspection
206
+ point and the joint check that follows), then the bench test. With
207
+ contributed `configure` operations the test is split around them: power-up
208
+ checks (before power, power-up, rails, current), the configuration, then
209
+ the checks that need firmware (programming, bus scans, smoke tests). Steps
210
+ that must follow the test come next: a step that fits propellers, or one
211
+ the doc spec marks `after: "test"` (the quadcopter's battery); then a final
212
+ check with the functional checks on the finished product. A sub-unit with
213
+ such a step gets a `REL_AFTER_TEST_IN_SUBUNIT` warning, since it is built
214
+ before the product's own test. Kitting: what
215
+ each operation consumes and where it comes from (ordering-list line,
216
+ fabrication order, sub-unit folder, the part it ships with).
217
+ - **Assembly guide.** Contributed by ext-docs (illustrated steps); the
218
+ release's plain steps only when no other extension contributes, from the
219
+ same plan as the work order (its "Assembly guide step N" is step N). A part
220
+ whose connectors ship loose (UHD `connector` trait `supplied: "loose"`, a
221
+ shield's header strips) gets a soldering step first, with a solder-joint
222
+ check. A mate the scene could not place (no geometry) lowers the
223
+ component-side part onto the structure-side one (the shield onto the UNO).
224
+ - **Test plan** (basic v0.1, marked so). First-article inspection of each
225
+ part designed in the project: overall size from its GLB, thickness, hole
226
+ patterns from its bolt-pattern interfaces, seal glands, features present;
227
+ boards bare and assembled; cables' length and conductor order. One joint
228
+ check per assembly step. Bring-up of what the unit powers: before power,
229
+ power-up current-limited (a battery's voltage in its place, a board's
230
+ undriven edge nets, power inputs nothing in the unit feeds), each rail at
231
+ its design voltage (board nets, regulated outputs of purchased modules),
232
+ current against the model's supply budget (`supply_budget`) and
233
+ `docs/power-budget.md` when the project has one, programming and boot on
234
+ the reachable USB/SWD/JTAG ports, an I2C scan per controller with the
235
+ addresses the parts state, and a smoke test per signal link. Functional
236
+ checks, one per requirement of `docs/brief.md` (ID tables, requirement
237
+ tables, top-level and requirement-section bullets), on the root; a
238
+ table's How, Pass and Record columns give the check's method and
239
+ criterion, otherwise they follow the requirement's wording. A rail that
240
+ passes an input through (the same voltage range as one of the part's
241
+ inputs) reads the supply's voltage. An input the model marks as left
242
+ unconnected on purpose (UHD `ChildModuleRef.unconnected`, with a reason)
243
+ gets no bench supply; when a rail can only read such an input (a motor
244
+ shield's outputs with its motor terminal open), a "Temporary supply" check
245
+ on that input comes first and those rails are measured with it on. The
246
+ I2C scan lists every address a device answers at: its `i2c_address` and
247
+ each `i2c_address_<name>` it states (a PCA9685's all-call 0x70). Project checks (`release.checks`) join
248
+ the bring-up, numbered power-phase first. Records to
249
+ capture, and `files/test/test-record.csv` with one row per check.
250
+ - **Builds that differ.** A unit named by module id is built once per
251
+ instance from one plan. Where the instances differ in what their parent
252
+ sets (`spin`, `overrides`), the plan names the value per build where a step
253
+ depends on it (the propeller's hand by the motor's spin at each position),
254
+ the work order and guide say how the builds differ, and a difference no
255
+ step accounts for is a `REL_BUILDS_DIFFER` warning.
256
+ - **Counts.** A wiring step's actions and its joint check count the same
257
+ thing: the conductors of the step's table (pin rows), in links.
258
+
259
+ Providers add their parts: ext-tscircuit the board fabrication and assembly
260
+ order, ext-frame-cad the cut order and its inspection, ext-enclosure-cad the
261
+ print order and the enclosure checks, ext-harness-cad cable preparation,
262
+ terminations and routing checks, ext-docs the spec sheet's designed parts
263
+ and the illustrated assembly guide, ext-drone the flight-controller
264
+ configuration and bench check.
265
+
266
+ ## Reproducibility
267
+
268
+ The manifest holds no clock time and no machine path. The release date is
269
+ `SOURCE_DATE_EPOCH`, else the source commit's date. Chrome writes the print
270
+ time and its DOM node counter into each PDF; both are normalised (same
271
+ length, so the cross-reference table holds). Figures are rendered with
272
+ SwiftShader. The fonts the PDFs were printed with are listed with digests,
273
+ since other fonts give other bytes. Rebuilding a version replaces its folder.
274
+
275
+ ## Tests
276
+
277
+ `test/release.test.ts` (from the repository root, `npm test`): requirements
278
+ read from a brief, test-id ranges, an earlier package not held against the
279
+ project, unit folding
280
+ and invalid entries; unit BOMs with the provider's BOM merged; ordering
281
+ quantities with spares, packs, MOQ, offers from both offline sources and
282
+ included parts; each precondition refusing (check errors, edited, extra and
283
+ missing outputs, a changed config, an earlier module, no export, uncommitted
284
+ changes); PDF normalisation; and, with Chrome, the package layout, files
285
+ from the standard outputs, the manifest, operations and test ids in
286
+ index.html, a CLI refusal, and two releases with identical bytes. The
287
+ fixture uses core's fake providers. Contributors test their parts with
288
+ `@protoboardai/ext-release/testing` (ext-docs, ext-frame-cad and
289
+ ext-harness-cad on the quadcopter, ext-enclosure-cad on a written build
290
+ report, ext-tscircuit with the IMU tile board, which also exercises the
291
+ release's bring-up checks for a custom board).
@@ -0,0 +1,59 @@
1
+ {
2
+ "schema": "protoboard.eval-case/v0",
3
+ "title": "Cut a release of the smart bottle after a generated file was edited by hand",
4
+ "skills": ["release", "circuit-board", "enclosure-cad"],
5
+ "tags": ["release", "package"],
6
+ "requires": ["chrome", "python-build123d", "tscircuit-node"],
7
+ "fixture": {
8
+ "from": "../../../../examples/smart-bottle",
9
+ "exclude": ["release"],
10
+ "operations": true,
11
+ "steps": [
12
+ { "note": "the seed: someone swapped the LED driver's MPN in the board's generated BOM", "replace": { "file": "src/boards/bottle-cap/out/bom.csv", "find": "LP5024RSMR", "with": "LP5024RSMT", "count": 1 } }
13
+ ]
14
+ },
15
+ "instruction": "Cut release 0.1.1 of the smart bottle with `npx protoboard release --version 0.1.1`: the Manufacturing Package for the cap board, the cap housing and final assembly. The design itself has not changed since 0.1.0 and must not change now: do not edit the UHD model, the parts or the module configs. Bring the project to a releasable state the way the release SOP says, cut the release, then check the package. Tell me what blocked the release and how you cleared it, and the package digest.",
16
+ "tools": { "read": true, "edit": true, "commands": ["npx protoboard *"] },
17
+ "budget": { "turns": 40, "seconds": 1800, "usd": 6 },
18
+ "graders": [
19
+ {
20
+ "id": "package-written",
21
+ "kind": "files",
22
+ "exist": [
23
+ "release/0.1.1/index.html",
24
+ "release/0.1.1/manifest.json",
25
+ "release/0.1.1/00-overview.pdf",
26
+ "release/0.1.1/system/work-order.pdf",
27
+ "release/0.1.1/system/ordering-list.csv",
28
+ "release/0.1.1/units/board/work-order.pdf",
29
+ "release/0.1.1/units/board/test-plan.pdf",
30
+ "release/0.1.1/units/cap/work-order.pdf"
31
+ ]
32
+ },
33
+ { "id": "overview-pdf", "kind": "format", "file": "release/0.1.1/00-overview.pdf", "format": "pdf" },
34
+ { "id": "manifest", "kind": "facts", "file": "release/0.1.1/manifest.json", "facts": [{ "path": "schema", "equals": "protoboard.release/v0" }, { "path": "version", "equals": "0.1.1" }, { "path": "check.errors", "equals": 0 }, { "path": "packageDigest", "matches": "[0-9a-f]{12}" }] },
35
+ { "id": "bom-as-exported", "kind": "digest", "file": "src/boards/bottle-cap/out/bom.csv", "equals": "sha256:cb00f59c0ab4281cab665f65686972ba1b0ae99f6390a61e60c7e96ba11343b2", "description": "the board's BOM is what circuit:export writes, not the hand edit" },
36
+ { "id": "package-bom-mpn", "kind": "text", "file": "release/0.1.1/units/board/bom.csv", "matches": ["LP5024RSMR"], "notMatches": ["LP5024RSMT"] },
37
+ { "id": "blocker-named", "kind": "text", "matches": ["bom\\.csv", "(edit|chang|modif)"], "description": "the answer names the hand-edited BOM" },
38
+ { "id": "model-untouched", "kind": "digest", "file": "src/boards/bottle-cap/bottle-cap.uhd.ts", "changed": false },
39
+ { "id": "circuit-config-untouched", "kind": "digest", "file": "src/boards/bottle-cap/circuit.config.ts", "changed": false },
40
+ { "id": "cad-config-untouched", "kind": "digest", "file": "src/enclosures/bottle-cap/cad.config.ts", "changed": false },
41
+ { "id": "project-config-untouched", "kind": "digest", "file": "protoboard.config.ts", "changed": false },
42
+ {
43
+ "id": "inspection",
44
+ "kind": "rubric",
45
+ "files": ["release/0.1.1/manifest.json"],
46
+ "rubric": "The agent cut release 0.1.1 of the smart bottle after a hand edit to a generated file blocked it. 1. The answer says what blocked the release (the board's out/bom.csv changed after circuit:export wrote it) and that it was cleared by re-running the generating operation, not by editing the file or the records, and names any further stale output it had to regenerate. 2. The release was not forced (no --allow-dirty, no edited operation records). 3. The answer reports the package digest as the manifest shows it. 4. The answer reports what it inspected in the package (units, documents, warnings carried into the manifest) and states remaining warnings honestly rather than claiming a clean bill of health."
47
+ }
48
+ ],
49
+ "solution": [
50
+ { "protoboard": ["release", "--check", "--version", "0.1.1"], "expect": { "exit": 1 } },
51
+ { "protoboard": ["run", "circuit:export", "--module", "bottle-cap-board"], "expect": { "status": "succeeded" } },
52
+ { "note": "the enclosure is stale only when the re-exported board body differs from the one it was designed around; regenerating it is the SOP either way", "protoboard": ["release", "--check", "--version", "0.1.1"] },
53
+ { "note": "the cap is fitted to the board body: regenerated whether or not the re-export changed it (a re-export that is not reproducible makes it ENCLOSURE_STALE, PB-869)", "protoboard": ["run", "cad:generate", "--module", "bottle-cap-enclosure"], "expect": { "status": "succeeded" } },
54
+ { "protoboard": ["run", "cad:export", "--module", "bottle-cap-enclosure"], "expect": { "status": "succeeded" } },
55
+ { "protoboard": ["release", "--check", "--version", "0.1.1"] },
56
+ { "protoboard": ["release", "--version", "0.1.1"], "expect": { "status": "succeeded" } },
57
+ { "say": "protoboard check reported PB_OUTPUT_STALE and release --check refused: src/boards/bottle-cap/out/bom.csv was changed after circuit:export wrote it (LP5024RSMR edited to LP5024RSMT). I re-ran circuit:export for bottle-cap-board, which restored the BOM (the same inputs, so the same board.step and board.glb), then regenerated and exported the cap enclosure around the re-exported board (the release SOP: a board export can make its enclosure stale). release --check then passed and release 0.1.1 wrote 3 units, 16 documents; the manifest's packageDigest is in release/0.1.1/manifest.json. The manifest carries 3 warnings: two FW_STRAPPING_PIN on the I2C lines and ENCLOSURE_THIN_WALL." }
58
+ ]
59
+ }