napkinstack 0.7.7__tar.gz → 0.9.0__tar.gz
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.
- {napkinstack-0.7.7 → napkinstack-0.9.0}/PKG-INFO +4 -4
- {napkinstack-0.7.7 → napkinstack-0.9.0}/README.md +3 -3
- {napkinstack-0.7.7 → napkinstack-0.9.0}/pyproject.toml +1 -1
- {napkinstack-0.7.7 → napkinstack-0.9.0}/pyproject.toml.orig +1 -1
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/doctor.py +6 -1
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/landed.py +30 -1
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/modules.py +2 -1
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/pull_request.py +29 -2
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/settings.py +8 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/skills.py +14 -1
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/templates/contracts/AGENTS.md +2 -1
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/templates/module/AGENTS.md +3 -3
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/templates/module/MANIFEST.yaml +0 -1
- napkinstack-0.9.0/src/napkinstack/templates/module/README.md +21 -0
- napkinstack-0.7.7/src/napkinstack/templates/module/README.md +0 -24
- napkinstack-0.7.7/src/napkinstack/templates/module/tests/.gitkeep +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/LICENSE +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/__init__.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/assurance.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/cli.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/compat.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/discovery.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/fitness/__init__.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/fitness/boundaries.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/fitness/hygiene.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/fitness/manifests.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/fitness/plan.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/fitness/pr_scope.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/project.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/provenance.py +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/templates/contracts/MANIFEST.yaml +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/templates/contracts/README.md +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/templates/contracts/docs/runbook.md +0 -0
- {napkinstack-0.7.7 → napkinstack-0.9.0}/src/napkinstack/templates/contracts/tests/.gitkeep +0 -0
- {napkinstack-0.7.7/src/napkinstack/templates/module/docs/adr → napkinstack-0.9.0/src/napkinstack/templates/module/src}/.gitkeep +0 -0
- {napkinstack-0.7.7/src/napkinstack/templates/module/src → napkinstack-0.9.0/src/napkinstack/templates/module/tests}/.gitkeep +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: napkinstack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Engineering framework for several teams and their agents working in one repository: modules, contracts, guardrails in CI.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -20,7 +20,7 @@ modules, contracts, guardrails in CI. On the Django or Rails model, one command
|
|
|
20
20
|
the project, which then receives new versions on demand; no application stack is imposed.
|
|
21
21
|
Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
|
|
22
22
|
|
|
23
|
-
> **Status: v0.
|
|
23
|
+
> **Status: v0.9.0, published** on [PyPI](https://pypi.org/project/napkinstack/).
|
|
24
24
|
> What each version changes, engine and project apart: [`CHANGELOG.md`](CHANGELOG.md).
|
|
25
25
|
> What is open: [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
|
|
26
26
|
|
|
@@ -84,8 +84,8 @@ To try it without installing anything, or to install it from this repository ins
|
|
|
84
84
|
registry — always pinned to a release tag:
|
|
85
85
|
|
|
86
86
|
```bash
|
|
87
|
-
uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.
|
|
88
|
-
uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.
|
|
87
|
+
uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.9.0" nstack init my-project
|
|
88
|
+
uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.9.0"
|
|
89
89
|
```
|
|
90
90
|
|
|
91
91
|
**Two channels, one published artefact.** The registry publishes: the PyPI artefact of a
|
|
@@ -5,7 +5,7 @@ modules, contracts, guardrails in CI. On the Django or Rails model, one command
|
|
|
5
5
|
the project, which then receives new versions on demand; no application stack is imposed.
|
|
6
6
|
Positioning and vocabulary: [`PRODUCT.md`](PRODUCT.md) §1.
|
|
7
7
|
|
|
8
|
-
> **Status: v0.
|
|
8
|
+
> **Status: v0.9.0, published** on [PyPI](https://pypi.org/project/napkinstack/).
|
|
9
9
|
> What each version changes, engine and project apart: [`CHANGELOG.md`](CHANGELOG.md).
|
|
10
10
|
> What is open: [`docs/governance/workstreams.md`](docs/governance/workstreams.md).
|
|
11
11
|
|
|
@@ -69,8 +69,8 @@ To try it without installing anything, or to install it from this repository ins
|
|
|
69
69
|
registry — always pinned to a release tag:
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
|
-
uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.
|
|
73
|
-
uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.
|
|
72
|
+
uvx --from "git+https://github.com/NapkinStack/engineering-os@v0.9.0" nstack init my-project
|
|
73
|
+
uv tool install "napkinstack @ git+https://github.com/NapkinStack/engineering-os@v0.9.0"
|
|
74
74
|
```
|
|
75
75
|
|
|
76
76
|
**Two channels, one published artefact.** The registry publishes: the PyPI artefact of a
|
|
@@ -30,7 +30,7 @@ Rules:
|
|
|
30
30
|
L7 the template source reachable by anyone: a repository, not a path on one machine
|
|
31
31
|
N1 information, never a gap (D91): a published version newer than the project's, read from
|
|
32
32
|
the registry — the one place a project learns of it. No other command opens a connection
|
|
33
|
-
G1-
|
|
33
|
+
G1-G19 the GitHub settings of CHECKLIST; G6 is not applicable outside a public
|
|
34
34
|
repository, and on a private one G1-G5, G12, G13, G17 and G18 name the GitHub plan
|
|
35
35
|
or option required
|
|
36
36
|
|
|
@@ -117,6 +117,10 @@ CHECKLIST = [ # (rule, setting, action)
|
|
|
117
117
|
# main, and said nothing of main being deleted or rewritten under them.
|
|
118
118
|
("G17", "Main branch cannot be deleted", f"{RULESET}: restrict deletions"),
|
|
119
119
|
("G18", "Main branch history cannot be rewritten: no force push", f"{RULESET}: block force pushes"),
|
|
120
|
+
# G19: every workflow and ruleset here watches `main`. A repository whose default branch is
|
|
121
|
+
# another one triggers none of them, and its record of commits on main never runs.
|
|
122
|
+
("G19", "Default branch `main`: the branch every workflow and ruleset here watches",
|
|
123
|
+
"Settings → General → Default branch: rename it main"),
|
|
120
124
|
]
|
|
121
125
|
|
|
122
126
|
# Settings specific to public repositories, and settings a private one pays for
|
|
@@ -267,6 +271,7 @@ CHECKS: dict[str, Callable[[GitHub], bool]] = {
|
|
|
267
271
|
"G16": lambda c: _merges(c, "delete_branch_on_merge").get("delete_branch_on_merge") is True,
|
|
268
272
|
"G17": lambda c: _rule(c, "deletion") is not None,
|
|
269
273
|
"G18": lambda c: _rule(c, "non_fast_forward") is not None,
|
|
274
|
+
"G19": lambda c: _merges(c, "default_branch").get("default_branch") == "main",
|
|
270
275
|
}
|
|
271
276
|
|
|
272
277
|
|
|
@@ -14,6 +14,10 @@ It says nothing about what predates it: the commit that created the repository,
|
|
|
14
14
|
everything the repository already held when the record arrived, are not reported. A record
|
|
15
15
|
that greets a project by failing on its own arrival teaches people to ignore it.
|
|
16
16
|
|
|
17
|
+
It reads from the last commit it recorded, not from the push alone: a push whose run never
|
|
18
|
+
started — CI out of minutes, a runner down — is read by the next run that does. Its last
|
|
19
|
+
success is read from the forge (actions: read); a record that never succeeded reads the push.
|
|
20
|
+
|
|
17
21
|
The forge is read with a token supplied by the run (GH_TOKEN, otherwise GITHUB_TOKEN), which
|
|
18
22
|
needs no write access of any kind: the project's automation token stays read-only (G10).
|
|
19
23
|
Without a token, or when the span cannot be read, nothing is claimed.
|
|
@@ -73,8 +77,28 @@ def _described(root: Path, commit: str) -> str:
|
|
|
73
77
|
return _git(root, "show", "-s", "--format=%h (%an, %ad)", "--date=short", commit).stdout.strip()
|
|
74
78
|
|
|
75
79
|
|
|
80
|
+
def _since_last_record(root: Path, span: str, forge: GitHub) -> tuple[str, str | None]:
|
|
81
|
+
"""The span widened to start at the last commit the record recorded, and that commit; the
|
|
82
|
+
span as given, and None, when the push already starts there or the forge cannot say."""
|
|
83
|
+
try:
|
|
84
|
+
runs = forge.get(f"/actions/workflows/{Path(RECORD).name}/runs?branch=main&status=success&per_page=1")
|
|
85
|
+
except NotVerified:
|
|
86
|
+
return span, None
|
|
87
|
+
found = (runs or {}).get("workflow_runs") or []
|
|
88
|
+
last = str(found[0].get("head_sha") or "") if found else ""
|
|
89
|
+
before, _, after = span.partition("..")
|
|
90
|
+
if not last or not after or last == before or _git(root, "cat-file", "-e", f"{last}^{{commit}}").returncode:
|
|
91
|
+
return span, None
|
|
92
|
+
if _git(root, "merge-base", "--is-ancestor", last, after).returncode:
|
|
93
|
+
return span, None # history rewritten: the push is all that is known
|
|
94
|
+
if before not in ("", EMPTY) and _git(root, "merge-base", "--is-ancestor", before, last).returncode == 0:
|
|
95
|
+
return span, None # the push starts after the last record already
|
|
96
|
+
return f"{last}..{after}", last
|
|
97
|
+
|
|
98
|
+
|
|
76
99
|
def run(root: Path, span: str, forge: GitHub | None) -> int:
|
|
77
|
-
|
|
100
|
+
pushed = _commits(root, span)
|
|
101
|
+
commits = pushed
|
|
78
102
|
if commits is None:
|
|
79
103
|
print(f"[W1] the commits of '{span}' cannot be read: not verified.\n"
|
|
80
104
|
" Action: fetch the history (fetch-depth: 0), or pass an existing range.")
|
|
@@ -83,6 +107,11 @@ def run(root: Path, span: str, forge: GitHub | None) -> int:
|
|
|
83
107
|
print("[W1] no token (GH_TOKEN or GITHUB_TOKEN): how each commit arrived is not verified.\n"
|
|
84
108
|
" Action: supply a token with read access to the repository's pull requests.")
|
|
85
109
|
return 0
|
|
110
|
+
span, last = _since_last_record(root, span, forge)
|
|
111
|
+
if last:
|
|
112
|
+
commits = _commits(root, span) or pushed
|
|
113
|
+
print(f"Read since the last commit it recorded, {last[:12]}: {len(commits) - len(pushed)} commit(s) "
|
|
114
|
+
"no run had read.")
|
|
86
115
|
beginning = _beginning(root)
|
|
87
116
|
outside, checked = [], 0
|
|
88
117
|
for commit in commits:
|
|
@@ -218,7 +218,8 @@ def next_steps(root: Path, name: str, criticality: str, user_facing: bool) -> li
|
|
|
218
218
|
"""What the module's first pull requests will be asked, from facts nstack holds (D33)."""
|
|
219
219
|
from napkinstack.fitness import plan # here: plan reads HANDLE from this module
|
|
220
220
|
|
|
221
|
-
steps = ["Creation ADR in docs/adr
|
|
221
|
+
steps = [f"Creation ADR in the project's docs/adr/, Scope: module {name}: capability, boundary, "
|
|
222
|
+
"alternatives — and the criticality "
|
|
222
223
|
f"with one sentence saying why {criticality} and not the value below or above; it is "
|
|
223
224
|
"the decider who settles it",
|
|
224
225
|
"MANIFEST.yaml: the responsibility in ONE sentence",
|
|
@@ -52,6 +52,8 @@ from napkinstack import assurance
|
|
|
52
52
|
from napkinstack.fitness.manifests import MODULE_DIRS, changed_files, find_manifests, is_description
|
|
53
53
|
from napkinstack.modules import LOGIN
|
|
54
54
|
|
|
55
|
+
CHARTER = plan.CHARTER.as_posix() # docs/project/charter.md, as the diff names it
|
|
56
|
+
|
|
55
57
|
LABEL = "out-of-cycle"
|
|
56
58
|
DELIVERABLE = re.compile(r"^Deliverable:[ \t]*(D[1-9][0-9]*)\b", re.I | re.M)
|
|
57
59
|
JUSTIFICATION = re.compile(r"^Out of cycle:[ \t]*(\S.*)$", re.I | re.M)
|
|
@@ -320,7 +322,8 @@ def check_sheet(body: str, head: str, reasons: list[str], fail: Fail,
|
|
|
320
322
|
return human_only
|
|
321
323
|
|
|
322
324
|
|
|
323
|
-
def check_cycle(root: Path, body: str, labels: set[str], today: datetime.date, fail: Fail
|
|
325
|
+
def check_cycle(root: Path, body: str, labels: set[str], today: datetime.date, fail: Fail,
|
|
326
|
+
warn: Callable[[str], None] = lambda message: None) -> None:
|
|
324
327
|
"""K1 to K4, for delivery work."""
|
|
325
328
|
if LABEL in labels:
|
|
326
329
|
if not JUSTIFICATION.search(body):
|
|
@@ -362,6 +365,27 @@ def check_cycle(root: Path, body: str, labels: set[str], today: datetime.date, f
|
|
|
362
365
|
elif states[match[1]] not in {"ready", "in-progress"}:
|
|
363
366
|
fail("K3", f"deliverable {match[1]} is '{states[match[1]]}': work starts on a ready "
|
|
364
367
|
"deliverable (definition of ready).")
|
|
368
|
+
elif states[match[1]] == "ready":
|
|
369
|
+
# Said, not refused: the state is the decider's record, and this pull request knows (PDR-0002).
|
|
370
|
+
warn(f"[K3] deliverable {match[1]} is still 'ready' while this pull request delivers it.\n"
|
|
371
|
+
f" Action: set its state: in-progress in {path.name}, in this pull request.")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def charter_without_pdr(root: Path, base: str, files: list[str]) -> list[str]:
|
|
375
|
+
"""C9: an accepted charter changes only through a PDR (it is the project's founding
|
|
376
|
+
decision). Said as a warning where the change is — a typo in it is not a decision — and
|
|
377
|
+
never while the charter is still proposed, which is when the framing writes it."""
|
|
378
|
+
if CHARTER not in files:
|
|
379
|
+
return []
|
|
380
|
+
before = _git(root, "show", f"{base}:{CHARTER}")
|
|
381
|
+
status = re.search(r"^status:\s*['\"]?(\w+)", before.stdout.split("\n---", 1)[0], re.M) if before.returncode == 0 else None
|
|
382
|
+
if not status or status.group(1) != "accepted":
|
|
383
|
+
return []
|
|
384
|
+
if any(path.startswith("docs/pdr/") and not path.endswith("README.md") for path in files):
|
|
385
|
+
return []
|
|
386
|
+
return [f"[C9] {CHARTER}: the accepted charter changes, and no decision record comes with it.\n"
|
|
387
|
+
" Action: write the PDR that decides the change, and cite it in the charter; a "
|
|
388
|
+
"correction that decides nothing needs none (.nstack/playbooks/framing.md, Decisions)."]
|
|
365
389
|
|
|
366
390
|
|
|
367
391
|
def run(root: Path, base: str, body_file: Path | None = None) -> int:
|
|
@@ -383,6 +407,7 @@ def run(root: Path, base: str, body_file: Path | None = None) -> int:
|
|
|
383
407
|
labels = {label.strip() for label in os.environ.get("PR_LABELS", "").split(",") if label.strip()}
|
|
384
408
|
|
|
385
409
|
failures: list[str] = []
|
|
410
|
+
warnings = charter_without_pdr(root, base, files)
|
|
386
411
|
|
|
387
412
|
def fail(rule: str, message: str) -> None:
|
|
388
413
|
failures.append(f"[{rule}] {message}")
|
|
@@ -397,7 +422,7 @@ def run(root: Path, base: str, body_file: Path | None = None) -> int:
|
|
|
397
422
|
human_only = check_sheet(body, head, reasons, fail, authorship, rerun_at_head(modules),
|
|
398
423
|
lambda commit: bool(tree_of(commit)) and tree_of(commit) == tree_of(head))
|
|
399
424
|
if modules:
|
|
400
|
-
check_cycle(root, body, labels, datetime.date.today(), fail)
|
|
425
|
+
check_cycle(root, body, labels, datetime.date.today(), fail, warnings.append)
|
|
401
426
|
|
|
402
427
|
print(f"Modules touched : {len(modules)}" + "".join(f"\n - {folder}" for folder in modules))
|
|
403
428
|
print(f"Test sheet : {'required' if reasons else 'not required'}")
|
|
@@ -405,6 +430,8 @@ def run(root: Path, base: str, body_file: Path | None = None) -> int:
|
|
|
405
430
|
print(line)
|
|
406
431
|
for scenario in human_only:
|
|
407
432
|
print(f" For the approver, human only: {scenario}")
|
|
433
|
+
for warning in warnings:
|
|
434
|
+
print(f"WARNING {warning}")
|
|
408
435
|
for failure in failures:
|
|
409
436
|
print(f"FAIL {failure}")
|
|
410
437
|
if failures:
|
|
@@ -142,6 +142,14 @@ def plan(client: doctor.GitHub | None, missing: set[str]) -> tuple[list[Call], l
|
|
|
142
142
|
merges["delete_branch_on_merge"] = True
|
|
143
143
|
if merges:
|
|
144
144
|
calls.append((", ".join(rule for rule in ("G14", "G15", "G16") if rule in missing), "PATCH", "", merges))
|
|
145
|
+
if "G19" in missing:
|
|
146
|
+
current = "<the default branch>"
|
|
147
|
+
if client is not None:
|
|
148
|
+
try:
|
|
149
|
+
current = str(client.get("").get("default_branch") or current)
|
|
150
|
+
except doctor.NotVerified:
|
|
151
|
+
pass
|
|
152
|
+
calls.append(("G19", "POST", f"/branches/{current}/rename", {"new_name": "main"}))
|
|
145
153
|
return calls, notes
|
|
146
154
|
|
|
147
155
|
|
|
@@ -48,11 +48,24 @@ def normalise(text: str) -> str:
|
|
|
48
48
|
return " ".join(text.split())
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
def trigger(playbook: str) -> str:
|
|
52
|
+
"""A playbook's trigger, as its header says it — the kernel's words (§4), on one line."""
|
|
53
|
+
match = re.search(r"^> \*\*Trigger\.\*\* (.+?)(?=\n>\s*\n|\n\n|\Z)", playbook, re.S | re.M)
|
|
54
|
+
return normalise(match.group(1).replace("\n> ", " ")) if match else ""
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def described(entry: dict, playbook: str) -> str:
|
|
58
|
+
"""What the skill holds, from the mapping, and when it loads, from its playbook: one list of
|
|
59
|
+
triggers, the kernel's, which the playbook's header says again word for word."""
|
|
60
|
+
when = trigger(playbook)
|
|
61
|
+
return normalise(entry["description"]) + (f" {when}" if when else "")
|
|
62
|
+
|
|
63
|
+
|
|
51
64
|
def build(root: Path, name: str, entry: dict) -> tuple[Path, str]:
|
|
52
65
|
body = (root / entry["source"]).read_text(encoding="utf-8")
|
|
53
66
|
# Serialised, never concatenated: a ":" or "#" in a description would break the YAML.
|
|
54
67
|
frontmatter = yaml.safe_dump(
|
|
55
|
-
{"name": name, "description":
|
|
68
|
+
{"name": name, "description": described(entry, body)},
|
|
56
69
|
allow_unicode=True, sort_keys=False, width=float("inf"),
|
|
57
70
|
)
|
|
58
71
|
content = "---\n" + frontmatter + "---\n\n" + BANNER.format(source=entry["source"]) + "\n\n" + body
|
|
@@ -22,5 +22,6 @@ contract tests do.
|
|
|
22
22
|
|
|
23
23
|
## Before changing anything
|
|
24
24
|
|
|
25
|
-
Check
|
|
25
|
+
Check who consumes it, read from the manifests: `nstack modules --with-contract-sides`, and
|
|
26
|
+
B6 for a version consumed and provided by nobody. A version is only removed once
|
|
26
27
|
`consumers = 0`.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
## Responsibility
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
The one sentence of `MANIFEST.yaml` (`module.responsibility`); not repeated here.
|
|
12
12
|
|
|
13
13
|
## What this module does not do
|
|
14
14
|
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
|
|
25
25
|
## Known traps
|
|
26
26
|
|
|
27
|
-
<
|
|
28
|
-
.nstack/handbook/10-measurement.md §4.>
|
|
27
|
+
<One line per trap — the situation, then what to do. Every entry becomes a test
|
|
28
|
+
(.nstack/handbook/10-measurement.md §4); a story of what happened goes to the log issue.>
|
|
29
29
|
|
|
30
30
|
## Areas not to change without approval
|
|
31
31
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# {{MODULE_NAME}}
|
|
2
|
+
|
|
3
|
+
What this module is for, who owns it and how critical it is: its
|
|
4
|
+
[MANIFEST.yaml](./MANIFEST.yaml), the one place they are written.
|
|
5
|
+
|
|
6
|
+
<A diagram, with its legend: what this module provides, what it consumes, who uses it.>
|
|
7
|
+
|
|
8
|
+
## Getting started
|
|
9
|
+
|
|
10
|
+
The standard verbs are declared in the `commands` section of the [manifest](./MANIFEST.yaml),
|
|
11
|
+
for this module's stack. Which they are, and what each one must do:
|
|
12
|
+
`.nstack/handbook/09-platform.md` §2.
|
|
13
|
+
|
|
14
|
+
## Contracts
|
|
15
|
+
|
|
16
|
+
- Provided: see `provides` in the MANIFEST
|
|
17
|
+
- Consumed: see `consumes` in the MANIFEST
|
|
18
|
+
|
|
19
|
+
## Decisions
|
|
20
|
+
|
|
21
|
+
In the project's `docs/adr/` and `docs/pdr/`, marked `Scope: module {{MODULE_NAME}}`.
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
# {{MODULE_NAME}}
|
|
2
|
-
|
|
3
|
-
<One sentence: what this module is for.>
|
|
4
|
-
|
|
5
|
-
- **Owner**: {{OWNER}}
|
|
6
|
-
- **Criticality**: {{CRITICALITY}}
|
|
7
|
-
- **Manifest**: [MANIFEST.yaml](./MANIFEST.yaml)
|
|
8
|
-
|
|
9
|
-
<A diagram, with its legend: what this module provides, what it consumes, who uses it.>
|
|
10
|
-
|
|
11
|
-
## Getting started
|
|
12
|
-
|
|
13
|
-
The standard verbs — `bootstrap`, `check`, `test`, `run` — are declared in the `commands`
|
|
14
|
-
section of the [manifest](./MANIFEST.yaml), for this module's stack. What each one must do:
|
|
15
|
-
`.nstack/handbook/09-platform.md` §2.
|
|
16
|
-
|
|
17
|
-
## Contracts
|
|
18
|
-
|
|
19
|
-
- Provided: see `provides` in the MANIFEST
|
|
20
|
-
- Consumed: see `consumes` in the MANIFEST
|
|
21
|
-
|
|
22
|
-
## Decisions
|
|
23
|
-
|
|
24
|
-
See `docs/adr/`.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|