@windyroad/itil 0.56.0-preview.879 → 0.56.0-preview.880
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/bin/wr-itil-mark-story-oversight-confirmed +51 -0
- package/lib/story-oversight.sh +51 -0
- package/package.json +1 -1
- package/scripts/check-rfc-stories-ratified.sh +7 -1
- package/scripts/detect-unratified-stories-maps.sh +19 -18
- package/scripts/mark-story-oversight-confirmed.sh +72 -0
- package/scripts/test/check-rfc-stories-ratified.bats +24 -12
- package/scripts/test/detect-unratified-stories-maps.bats +27 -17
- package/scripts/test/mark-story-oversight-confirmed.bats +53 -0
- package/scripts/test/story-oversight-lib.bats +64 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Generated by scripts/sync-shim-wrappers.sh from
|
|
3
|
+
# packages/shared/lib/shim-wrapper-template.sh. DO NOT EDIT individual
|
|
4
|
+
# shim files in packages/*/bin/wr-* directly; edit the template + run
|
|
5
|
+
# `npm run sync:shim-wrappers` to regenerate.
|
|
6
|
+
#
|
|
7
|
+
# Resolution (ADR-080):
|
|
8
|
+
# 1. If the wrapper's parent dir is semver-shaped, treat as installed-
|
|
9
|
+
# cache execution and resolve to the highest-version sibling's
|
|
10
|
+
# scripts/ entry below.
|
|
11
|
+
# 2. Otherwise (parent dir is e.g. `architect`), treat as source-
|
|
12
|
+
# monorepo execution and dispatch to own scripts/. The source-repo-
|
|
13
|
+
# guard `exec` is the anchor parsed by
|
|
14
|
+
# packages/retrospective/scripts/check-tarball-shipped-shims.sh.
|
|
15
|
+
# 3. If the cache parent contains zero semver-shaped siblings, exit
|
|
16
|
+
# 127 with a stderr message naming the cache parent (per SQ-080-2).
|
|
17
|
+
#
|
|
18
|
+
# @adr ADR-080 (highest-version-wins shim wrapper plugin scaffold)
|
|
19
|
+
# @adr ADR-049 (plugin-bundled scripts resolve via bin/ on $PATH — amended)
|
|
20
|
+
# @problem P343 (mid-session staleness window)
|
|
21
|
+
|
|
22
|
+
set -euo pipefail
|
|
23
|
+
|
|
24
|
+
SHIM_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
25
|
+
OWN_VERSION_DIR="$(dirname "$SHIM_DIR")"
|
|
26
|
+
OWN_VERSION_NAME="$(basename "$OWN_VERSION_DIR")"
|
|
27
|
+
CACHE_PARENT="$(dirname "$OWN_VERSION_DIR")"
|
|
28
|
+
|
|
29
|
+
SEMVER_RE='^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'
|
|
30
|
+
|
|
31
|
+
# Source-repo guard: own parent dir is NOT semver → dispatch to own scripts/.
|
|
32
|
+
if ! [[ "$OWN_VERSION_NAME" =~ $SEMVER_RE ]]; then
|
|
33
|
+
exec "$SHIM_DIR/../scripts/mark-story-oversight-confirmed.sh" "$@"
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Cache execution: pick the highest-semver sibling under CACHE_PARENT.
|
|
37
|
+
HIGHEST=""
|
|
38
|
+
while IFS= read -r dir; do
|
|
39
|
+
name="$(basename "$dir")"
|
|
40
|
+
[[ "$name" =~ $SEMVER_RE ]] || continue
|
|
41
|
+
if [[ -z "$HIGHEST" ]] || [[ "$(printf '%s\n%s\n' "$HIGHEST" "$name" | sort -V | tail -1)" == "$name" ]]; then
|
|
42
|
+
HIGHEST="$name"
|
|
43
|
+
fi
|
|
44
|
+
done < <(find "$CACHE_PARENT" -mindepth 1 -maxdepth 1 -type d 2>/dev/null)
|
|
45
|
+
|
|
46
|
+
if [[ -z "$HIGHEST" ]]; then
|
|
47
|
+
printf 'wr-shim: no cached versions in %s\n' "$CACHE_PARENT" >&2
|
|
48
|
+
exit 127
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
exec "$CACHE_PARENT/$HIGHEST/scripts/mark-story-oversight-confirmed.sh" "$@"
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# story-oversight.sh — ADR-090 lazy-fingerprint ratification helpers.
|
|
3
|
+
#
|
|
4
|
+
# Shared by detect-unratified-stories-maps.sh, check-rfc-stories-ratified.sh, and
|
|
5
|
+
# mark-story-oversight-confirmed.sh so all three agree on ONE hash definition
|
|
6
|
+
# (if they diverged, a freshly-ratified artefact would read as drifted forever).
|
|
7
|
+
#
|
|
8
|
+
# A story/map is RATIFIED when it carries a `confirmed` human-oversight marker
|
|
9
|
+
# AND a stored oversight-hash that matches a fresh hash of its content-minus-
|
|
10
|
+
# marker. Any content edit changes the hash → the artefact reads as drifted /
|
|
11
|
+
# unratified until re-ratified. This is ADR-090's drift-invalidation (ADR-009
|
|
12
|
+
# drift lineage, NOT ADR-066 write-once) — the same hash-the-artefact pattern
|
|
13
|
+
# the external-comms gate uses.
|
|
14
|
+
#
|
|
15
|
+
# Sourced, not executed. Requires `shasum` (BSD + coreutils both ship it).
|
|
16
|
+
|
|
17
|
+
# Stable hash of the artefact's content EXCLUDING the two marker lines, so
|
|
18
|
+
# writing/updating the marker is idempotent w.r.t. the hash. Covers both
|
|
19
|
+
# encodings in one filter:
|
|
20
|
+
# - markdown: `human-oversight:` / `oversight-hash:` frontmatter lines
|
|
21
|
+
# - HTML: <meta name="human-oversight" ...> / <meta name="oversight-hash" ...>
|
|
22
|
+
oversight_content_hash() {
|
|
23
|
+
grep -vE '^(human-oversight|oversight-hash):|<meta[^>]*name="(human-oversight|oversight-hash)"' "$1" \
|
|
24
|
+
| shasum -a 256 | awk '{print $1}'
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# Echo the stored oversight-hash (md frontmatter OR HTML meta), empty if none.
|
|
28
|
+
oversight_stored_hash() {
|
|
29
|
+
local h
|
|
30
|
+
h="$(grep -oE '^oversight-hash:[[:space:]]*[a-f0-9]{64}' "$1" 2>/dev/null | grep -oE '[a-f0-9]{64}' | head -1)"
|
|
31
|
+
[ -z "$h" ] && h="$(grep -oE '<meta[^>]*name="oversight-hash"[^>]*content="[a-f0-9]{64}"' "$1" 2>/dev/null | grep -oE '[a-f0-9]{64}' | head -1)"
|
|
32
|
+
printf '%s' "$h"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# True (0) if the file carries a `confirmed` human-oversight marker (md or HTML).
|
|
36
|
+
oversight_is_confirmed() {
|
|
37
|
+
grep -qiE '^human-oversight:[[:space:]]*confirmed([[:space:]]|$)' "$1" && return 0
|
|
38
|
+
grep -qiE '<meta[^>]*name="human-oversight"[^>]*content="confirmed"' "$1" && return 0
|
|
39
|
+
return 1
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# True (0) if RATIFIED: confirmed AND a stored hash that matches current content.
|
|
43
|
+
# A confirmed marker with NO stored hash (legacy hand-ratified) is NOT ratified —
|
|
44
|
+
# it must be re-ratified once to gain its fingerprint.
|
|
45
|
+
is_story_map_ratified() {
|
|
46
|
+
local f="$1" stored
|
|
47
|
+
oversight_is_confirmed "$f" || return 1
|
|
48
|
+
stored="$(oversight_stored_hash "$f")"
|
|
49
|
+
[ -z "$stored" ] && return 1
|
|
50
|
+
[ "$stored" = "$(oversight_content_hash "$f")" ]
|
|
51
|
+
}
|
package/package.json
CHANGED
|
@@ -17,6 +17,12 @@
|
|
|
17
17
|
# Authority: ADR-090. Driver: P404 Phase 2. Test: check-rfc-stories-ratified.bats.
|
|
18
18
|
set -euo pipefail
|
|
19
19
|
|
|
20
|
+
# Adopter-safe: source the shared lazy-fingerprint lib RELATIVE TO THIS SCRIPT (P317).
|
|
21
|
+
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)" || {
|
|
22
|
+
echo "check-rfc-stories-ratified: cannot locate lib dir" >&2; exit 2; }
|
|
23
|
+
# shellcheck source=/dev/null
|
|
24
|
+
source "$LIB/story-oversight.sh"
|
|
25
|
+
|
|
20
26
|
rfc="${1:-}"
|
|
21
27
|
stories_root="${2:-docs/stories}"
|
|
22
28
|
if [ -z "$rfc" ]; then
|
|
@@ -45,7 +51,7 @@ for id in $ids; do
|
|
|
45
51
|
unratified="$unratified $id(missing)"
|
|
46
52
|
continue
|
|
47
53
|
fi
|
|
48
|
-
if !
|
|
54
|
+
if ! is_story_map_ratified "$f"; then
|
|
49
55
|
unratified="$unratified $id(unratified)"
|
|
50
56
|
fi
|
|
51
57
|
done
|
|
@@ -1,42 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# detect-unratified-stories-maps.sh — ADR-090 detector.
|
|
3
3
|
#
|
|
4
|
-
# Token-cheap detection of story maps + stories
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# (
|
|
4
|
+
# Token-cheap detection of story maps + stories that are NOT ratified. Mirrors
|
|
5
|
+
# wr-architect detect-unoversighted.sh but spans two artefact types and is
|
|
6
|
+
# DRIFT-AWARE via the shared lazy-fingerprint helper: an artefact is ratified
|
|
7
|
+
# only when it carries a `confirmed` human-oversight marker AND a stored
|
|
8
|
+
# oversight-hash matching its current content. This surfaces three cases as the
|
|
9
|
+
# drain queue:
|
|
10
|
+
# - never ratified (no marker)
|
|
11
|
+
# - drift-reopened (marker says confirmed, but content changed → hash mismatch)
|
|
12
|
+
# - legacy confirmed (confirmed but no fingerprint yet — needs one re-ratify)
|
|
13
13
|
#
|
|
14
14
|
# Usage: detect-unratified-stories-maps.sh [STORIES_DIR=docs/stories] [MAPS_DIR=docs/story-maps]
|
|
15
|
-
# Output: one unratified artefact path per line, sorted. Empty = all
|
|
15
|
+
# Output: one unratified artefact path per line, sorted. Empty = all ratified.
|
|
16
16
|
# Always exits 0 (detector, not a gate). Consumed by the work-problems Step 2.4
|
|
17
|
-
# oversight-unconfirmed drain
|
|
17
|
+
# oversight-unconfirmed drain.
|
|
18
18
|
set -euo pipefail
|
|
19
19
|
|
|
20
|
+
# Adopter-safe: source the shared hash lib RELATIVE TO THIS SCRIPT (P317).
|
|
21
|
+
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)" || {
|
|
22
|
+
echo "detect-unratified-stories-maps: cannot locate lib dir" >&2; exit 0; }
|
|
23
|
+
# shellcheck source=/dev/null
|
|
24
|
+
source "$LIB/story-oversight.sh"
|
|
25
|
+
|
|
20
26
|
STORIES_DIR="${1:-docs/stories}"
|
|
21
27
|
MAPS_DIR="${2:-docs/story-maps}"
|
|
22
28
|
|
|
23
29
|
shopt -s nullglob
|
|
24
30
|
|
|
25
31
|
{
|
|
26
|
-
# Stories — markdown YAML frontmatter.
|
|
27
32
|
if [ -d "$STORIES_DIR" ]; then
|
|
28
33
|
for f in "$STORIES_DIR"/*.md "$STORIES_DIR"/*/*.md; do
|
|
29
34
|
[ "$(basename "$f")" = "README.md" ] && continue
|
|
30
|
-
|
|
31
|
-
printf '%s\n' "$fm" | grep -qiE '^human-oversight:[[:space:]]*confirmed[[:space:]]*$' && continue
|
|
32
|
-
echo "$f"
|
|
35
|
+
is_story_map_ratified "$f" || echo "$f"
|
|
33
36
|
done
|
|
34
37
|
fi
|
|
35
|
-
# Story maps — HTML meta tag.
|
|
36
38
|
if [ -d "$MAPS_DIR" ]; then
|
|
37
39
|
for f in "$MAPS_DIR"/*.html "$MAPS_DIR"/*/*.html; do
|
|
38
|
-
|
|
39
|
-
echo "$f"
|
|
40
|
+
is_story_map_ratified "$f" || echo "$f"
|
|
40
41
|
done
|
|
41
42
|
fi
|
|
42
43
|
} | sort
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# mark-story-oversight-confirmed.sh — ADR-090 ratify write-path.
|
|
3
|
+
#
|
|
4
|
+
# Writes `human-oversight: confirmed` + an `oversight-hash` fingerprint of the
|
|
5
|
+
# current content into a story (markdown frontmatter) or story-map (HTML meta).
|
|
6
|
+
# Idempotent: re-running recomputes the hash and replaces any existing marker
|
|
7
|
+
# (never duplicates). A later content edit changes the content hash, so the
|
|
8
|
+
# stored fingerprint no longer matches → the artefact reads as drifted /
|
|
9
|
+
# unratified until re-ratified (ADR-090 drift-invalidation).
|
|
10
|
+
#
|
|
11
|
+
# Usage: mark-story-oversight-confirmed.sh <story-or-map-file>
|
|
12
|
+
# Exit: 0 = ratified; 2 = usage / file error.
|
|
13
|
+
#
|
|
14
|
+
# Authority: ADR-090. Driver: P404 Phase 2. Test: mark-story-oversight-confirmed.bats.
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# Adopter-safe: source the shared hash lib RELATIVE TO THIS SCRIPT (P317), never
|
|
18
|
+
# repo-relative.
|
|
19
|
+
LIB="$(cd "$(dirname "${BASH_SOURCE[0]}")/../lib" 2>/dev/null && pwd)" || {
|
|
20
|
+
echo "mark-story-oversight-confirmed: cannot locate lib dir" >&2; exit 2; }
|
|
21
|
+
# shellcheck source=/dev/null
|
|
22
|
+
source "$LIB/story-oversight.sh"
|
|
23
|
+
|
|
24
|
+
f="${1:-}"
|
|
25
|
+
if [ -z "$f" ]; then
|
|
26
|
+
echo "mark-story-oversight-confirmed: usage: mark-story-oversight-confirmed.sh <file>" >&2
|
|
27
|
+
exit 2
|
|
28
|
+
fi
|
|
29
|
+
[ -f "$f" ] || { echo "mark-story-oversight-confirmed: file not found: $f" >&2; exit 2; }
|
|
30
|
+
|
|
31
|
+
h="$(oversight_content_hash "$f")"
|
|
32
|
+
tmp="$(mktemp)"
|
|
33
|
+
|
|
34
|
+
case "$f" in
|
|
35
|
+
*.html)
|
|
36
|
+
if grep -qi '<head' "$f"; then
|
|
37
|
+
# Insert the two metas after the <head> tag; drop any existing marker metas.
|
|
38
|
+
awk -v H="$h" '
|
|
39
|
+
/<meta[^>]*name="(human-oversight|oversight-hash)"/ { next }
|
|
40
|
+
{ print }
|
|
41
|
+
!done && tolower($0) ~ /<head[ >]/ {
|
|
42
|
+
print " <meta name=\"human-oversight\" content=\"confirmed\">"
|
|
43
|
+
print " <meta name=\"oversight-hash\" content=\"" H "\">"
|
|
44
|
+
done=1
|
|
45
|
+
}
|
|
46
|
+
' "$f" > "$tmp"
|
|
47
|
+
else
|
|
48
|
+
# No <head> — prepend the metas (position is irrelevant to the hash).
|
|
49
|
+
{ printf '<meta name="human-oversight" content="confirmed">\n'
|
|
50
|
+
printf '<meta name="oversight-hash" content="%s">\n' "$h"
|
|
51
|
+
grep -vE '<meta[^>]*name="(human-oversight|oversight-hash)"' "$f"; } > "$tmp"
|
|
52
|
+
fi
|
|
53
|
+
;;
|
|
54
|
+
*)
|
|
55
|
+
# Markdown: rewrite frontmatter — drop existing markers, insert both before
|
|
56
|
+
# the closing `---`.
|
|
57
|
+
awk -v H="$h" '
|
|
58
|
+
NR==1 && $0=="---" { infm=1; print; next }
|
|
59
|
+
infm && /^(human-oversight|oversight-hash):/ { next }
|
|
60
|
+
infm && /^---[[:space:]]*$/ && !done {
|
|
61
|
+
print "human-oversight: confirmed"
|
|
62
|
+
print "oversight-hash: " H
|
|
63
|
+
print; done=1; infm=0; next
|
|
64
|
+
}
|
|
65
|
+
{ print }
|
|
66
|
+
' "$f" > "$tmp"
|
|
67
|
+
;;
|
|
68
|
+
esac
|
|
69
|
+
|
|
70
|
+
mv "$tmp" "$f"
|
|
71
|
+
echo "mark-story-oversight-confirmed: ratified $f (oversight-hash ${h:0:12}…)" >&2
|
|
72
|
+
exit 0
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
#!/usr/bin/env bats
|
|
2
2
|
# Behavioural test for check-rfc-stories-ratified.sh (ADR-090 — an RFC may
|
|
3
|
-
# reference only RATIFIED stories).
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# has-stories checks >=1 exists; this checks each listed one is ratified.
|
|
3
|
+
# reference only RATIFIED stories). DRIFT-AWARE via the shared lazy-fingerprint
|
|
4
|
+
# lib: a listed story counts only when it is `confirmed` AND its stored
|
|
5
|
+
# oversight-hash matches current content. Composes with check-rfc-has-stories
|
|
6
|
+
# (ADR-089): has-stories checks >=1 exists; this checks each listed one is ratified.
|
|
7
7
|
#
|
|
8
|
-
# @adr ADR-090
|
|
8
|
+
# @adr ADR-090
|
|
9
9
|
# @problem P404 (implement ADR-089 + ADR-090) — Phase 2
|
|
10
10
|
|
|
11
11
|
setup() {
|
|
12
12
|
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
13
13
|
SCRIPT="${REPO_ROOT}/packages/itil/scripts/check-rfc-stories-ratified.sh"
|
|
14
|
+
MARK="${REPO_ROOT}/packages/itil/scripts/mark-story-oversight-confirmed.sh"
|
|
14
15
|
TMPD="$(mktemp -d)"
|
|
15
16
|
mkdir -p "$TMPD/stories/accepted"
|
|
16
17
|
}
|
|
17
18
|
teardown() { rm -rf "$TMPD"; }
|
|
18
19
|
|
|
19
|
-
mkstory() { # $1=id $2=
|
|
20
|
+
mkstory() { # $1=id $2=confirmed|unconfirmed|none
|
|
20
21
|
local f="$TMPD/stories/accepted/$1-x.md"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
case "$2" in
|
|
23
|
+
confirmed) printf -- '---\nstatus: accepted\n---\n# %s\n' "$1" > "$f"; bash "$MARK" "$f" ;;
|
|
24
|
+
unconfirmed) printf -- '---\nstatus: accepted\nhuman-oversight: unconfirmed\n---\n# %s\n' "$1" > "$f" ;;
|
|
25
|
+
none) printf -- '---\nstatus: accepted\n---\n# %s\n' "$1" > "$f" ;;
|
|
26
|
+
esac
|
|
24
27
|
}
|
|
25
28
|
mkrfc() { printf -- '---\nstatus: proposed\nstories: [%s]\n---\n# rfc\n' "$1" > "$TMPD/RFC.proposed.md"; }
|
|
26
29
|
|
|
@@ -30,14 +33,14 @@ mkrfc() { printf -- '---\nstatus: proposed\nstories: [%s]\n---\n# rfc\n' "$1" >
|
|
|
30
33
|
[ "$status" -eq 0 ]
|
|
31
34
|
}
|
|
32
35
|
|
|
33
|
-
@test "check-rfc-stories-ratified: a confirmed story is ACCEPTED (exit 0)" {
|
|
36
|
+
@test "check-rfc-stories-ratified: a ratified (confirmed+hash) story is ACCEPTED (exit 0)" {
|
|
34
37
|
mkstory STORY-020 confirmed
|
|
35
38
|
mkrfc STORY-020
|
|
36
39
|
run bash "$SCRIPT" "$TMPD/RFC.proposed.md" "$TMPD/stories"
|
|
37
40
|
[ "$status" -eq 0 ]
|
|
38
41
|
}
|
|
39
42
|
|
|
40
|
-
@test "check-rfc-stories-ratified: an unconfirmed story is REJECTED
|
|
43
|
+
@test "check-rfc-stories-ratified: an unconfirmed story is REJECTED" {
|
|
41
44
|
mkstory STORY-020 unconfirmed
|
|
42
45
|
mkrfc STORY-020
|
|
43
46
|
run bash "$SCRIPT" "$TMPD/RFC.proposed.md" "$TMPD/stories"
|
|
@@ -53,6 +56,15 @@ mkrfc() { printf -- '---\nstatus: proposed\nstories: [%s]\n---\n# rfc\n' "$1" >
|
|
|
53
56
|
[[ "$output" == *"STORY-020"* ]]
|
|
54
57
|
}
|
|
55
58
|
|
|
59
|
+
@test "check-rfc-stories-ratified: a confirmed-but-DRIFTED story is REJECTED" {
|
|
60
|
+
mkstory STORY-020 confirmed
|
|
61
|
+
printf '\nedit after ratify\n' >> "$TMPD/stories/accepted/STORY-020-x.md"
|
|
62
|
+
mkrfc STORY-020
|
|
63
|
+
run bash "$SCRIPT" "$TMPD/RFC.proposed.md" "$TMPD/stories"
|
|
64
|
+
[ "$status" -ne 0 ]
|
|
65
|
+
[[ "$output" == *"STORY-020"* ]]
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
@test "check-rfc-stories-ratified: a missing story file is REJECTED" {
|
|
57
69
|
mkrfc STORY-099
|
|
58
70
|
run bash "$SCRIPT" "$TMPD/RFC.proposed.md" "$TMPD/stories"
|
|
@@ -60,7 +72,7 @@ mkrfc() { printf -- '---\nstatus: proposed\nstories: [%s]\n---\n# rfc\n' "$1" >
|
|
|
60
72
|
[[ "$output" == *"STORY-099"* ]]
|
|
61
73
|
}
|
|
62
74
|
|
|
63
|
-
@test "check-rfc-stories-ratified: mixed — one
|
|
75
|
+
@test "check-rfc-stories-ratified: mixed — one ratified one unconfirmed is REJECTED" {
|
|
64
76
|
mkstory STORY-020 confirmed
|
|
65
77
|
mkstory STORY-021 unconfirmed
|
|
66
78
|
mkrfc "STORY-020, STORY-021"
|
|
@@ -1,44 +1,54 @@
|
|
|
1
1
|
#!/usr/bin/env bats
|
|
2
2
|
# Behavioural test for detect-unratified-stories-maps.sh (ADR-090 detector).
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# Lists every artefact whose marker is missing or not `confirmed`. Always exit 0.
|
|
3
|
+
# DRIFT-AWARE via the shared lazy-fingerprint lib: an artefact counts as ratified
|
|
4
|
+
# only when it is `confirmed` AND its stored oversight-hash matches current
|
|
5
|
+
# content. Surfaces never-ratified, unconfirmed, and drift-reopened (confirmed
|
|
6
|
+
# then edited) cases; omits genuinely-ratified ones. Always exit 0.
|
|
8
7
|
#
|
|
9
|
-
# @adr ADR-090
|
|
10
|
-
# @problem P404 (
|
|
8
|
+
# @adr ADR-090
|
|
9
|
+
# @problem P404 (Phase 2)
|
|
11
10
|
|
|
12
11
|
setup() {
|
|
13
12
|
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
14
13
|
SCRIPT="${REPO_ROOT}/packages/itil/scripts/detect-unratified-stories-maps.sh"
|
|
14
|
+
MARK="${REPO_ROOT}/packages/itil/scripts/mark-story-oversight-confirmed.sh"
|
|
15
15
|
TMPD="$(mktemp -d)"
|
|
16
16
|
mkdir -p "$TMPD/stories/accepted" "$TMPD/story-maps/draft"
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
# Genuinely ratified (via the mark write-path) — should NOT be listed.
|
|
19
|
+
printf -- '---\nstatus: accepted\n---\n# ok\n' > "$TMPD/stories/accepted/STORY-1-ok.md"
|
|
20
|
+
bash "$MARK" "$TMPD/stories/accepted/STORY-1-ok.md"
|
|
21
|
+
printf -- '<head></head>\n<h1>ok map</h1>\n' > "$TMPD/story-maps/draft/STORY-MAP-1-ok.html"
|
|
22
|
+
bash "$MARK" "$TMPD/story-maps/draft/STORY-MAP-1-ok.html"
|
|
23
|
+
|
|
24
|
+
# Unconfirmed + unmarked — listed.
|
|
19
25
|
printf -- '---\nstatus: accepted\nhuman-oversight: unconfirmed\n---\n# no\n' > "$TMPD/stories/accepted/STORY-2-unconf.md"
|
|
20
26
|
printf -- '---\nstatus: accepted\n---\n# nomarker\n' > "$TMPD/stories/accepted/STORY-3-none.md"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
printf -- '
|
|
24
|
-
|
|
27
|
+
|
|
28
|
+
# Drifted: ratified, then content edited afterward — listed.
|
|
29
|
+
printf -- '---\nstatus: accepted\n---\n# body\n' > "$TMPD/stories/accepted/STORY-4-drift.md"
|
|
30
|
+
bash "$MARK" "$TMPD/stories/accepted/STORY-4-drift.md"
|
|
31
|
+
printf '\nAn edit made after ratification.\n' >> "$TMPD/stories/accepted/STORY-4-drift.md"
|
|
32
|
+
|
|
33
|
+
# HTML map with no marker — listed.
|
|
34
|
+
printf -- '<h1>no meta</h1>\n' > "$TMPD/story-maps/draft/STORY-MAP-3-none.html"
|
|
25
35
|
}
|
|
26
36
|
teardown() { rm -rf "$TMPD"; }
|
|
27
37
|
|
|
28
|
-
@test "detect: lists unconfirmed + unmarked, omits
|
|
38
|
+
@test "detect: lists unconfirmed + unmarked + DRIFTED, omits genuinely-ratified; exit 0" {
|
|
29
39
|
run bash "$SCRIPT" "$TMPD/stories" "$TMPD/story-maps"
|
|
30
40
|
[ "$status" -eq 0 ]
|
|
31
41
|
[[ "$output" == *"STORY-2-unconf.md"* ]]
|
|
32
42
|
[[ "$output" == *"STORY-3-none.md"* ]]
|
|
33
|
-
[[ "$output" == *"STORY-
|
|
43
|
+
[[ "$output" == *"STORY-4-drift.md"* ]]
|
|
34
44
|
[[ "$output" == *"STORY-MAP-3-none.html"* ]]
|
|
35
45
|
[[ "$output" != *"STORY-1-ok.md"* ]]
|
|
36
46
|
[[ "$output" != *"STORY-MAP-1-ok.html"* ]]
|
|
37
47
|
}
|
|
38
48
|
|
|
39
|
-
@test "detect: all-
|
|
49
|
+
@test "detect: all-ratified set yields empty output, exit 0" {
|
|
40
50
|
rm "$TMPD/stories/accepted/STORY-2-unconf.md" "$TMPD/stories/accepted/STORY-3-none.md" \
|
|
41
|
-
"$TMPD/
|
|
51
|
+
"$TMPD/stories/accepted/STORY-4-drift.md" "$TMPD/story-maps/draft/STORY-MAP-3-none.html"
|
|
42
52
|
run bash "$SCRIPT" "$TMPD/stories" "$TMPD/story-maps"
|
|
43
53
|
[ "$status" -eq 0 ]
|
|
44
54
|
[ -z "$output" ]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Behavioural test for mark-story-oversight-confirmed.sh (ADR-090 ratify write-path).
|
|
3
|
+
# Writes `confirmed` + the content fingerprint into a story (md frontmatter) or a
|
|
4
|
+
# story-map (HTML meta). After marking, is_story_map_ratified is true; a later
|
|
5
|
+
# content edit drifts it back to not-ratified.
|
|
6
|
+
#
|
|
7
|
+
# @adr ADR-090
|
|
8
|
+
# @problem P404 (Phase 2)
|
|
9
|
+
|
|
10
|
+
setup() {
|
|
11
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
12
|
+
SCRIPT="${REPO_ROOT}/packages/itil/scripts/mark-story-oversight-confirmed.sh"
|
|
13
|
+
# shellcheck source=/dev/null
|
|
14
|
+
source "${REPO_ROOT}/packages/itil/lib/story-oversight.sh"
|
|
15
|
+
TMPD="$(mktemp -d)"
|
|
16
|
+
}
|
|
17
|
+
teardown() { rm -rf "$TMPD"; }
|
|
18
|
+
|
|
19
|
+
@test "mark: md story with no marker → ratified after mark" {
|
|
20
|
+
printf -- '---\nstatus: accepted\n---\n# body\n' > "$TMPD/s.md"
|
|
21
|
+
run bash "$SCRIPT" "$TMPD/s.md"; [ "$status" -eq 0 ]
|
|
22
|
+
run is_story_map_ratified "$TMPD/s.md"; [ "$status" -eq 0 ]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
@test "mark: md story with existing unconfirmed marker → confirmed + ratified" {
|
|
26
|
+
printf -- '---\nstatus: accepted\nhuman-oversight: unconfirmed\n---\n# body\n' > "$TMPD/s.md"
|
|
27
|
+
bash "$SCRIPT" "$TMPD/s.md"
|
|
28
|
+
grep -qE '^human-oversight:[[:space:]]*confirmed' "$TMPD/s.md"
|
|
29
|
+
run is_story_map_ratified "$TMPD/s.md"; [ "$status" -eq 0 ]
|
|
30
|
+
# no duplicate marker line left behind
|
|
31
|
+
[ "$(grep -cE '^human-oversight:' "$TMPD/s.md")" -eq 1 ]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
@test "mark: idempotent — marking twice stays ratified, single marker" {
|
|
35
|
+
printf -- '---\nstatus: accepted\n---\n# body\n' > "$TMPD/s.md"
|
|
36
|
+
bash "$SCRIPT" "$TMPD/s.md"; bash "$SCRIPT" "$TMPD/s.md"
|
|
37
|
+
run is_story_map_ratified "$TMPD/s.md"; [ "$status" -eq 0 ]
|
|
38
|
+
[ "$(grep -cE '^oversight-hash:' "$TMPD/s.md")" -eq 1 ]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@test "mark: drift — mark then edit body → not ratified" {
|
|
42
|
+
printf -- '---\nstatus: accepted\n---\n# body\n' > "$TMPD/s.md"
|
|
43
|
+
bash "$SCRIPT" "$TMPD/s.md"
|
|
44
|
+
printf -- '---\nstatus: accepted\nhuman-oversight: confirmed\n---\n# body EDITED\n' > "$TMPD/s.md"
|
|
45
|
+
run is_story_map_ratified "$TMPD/s.md"; [ "$status" -ne 0 ]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@test "mark: HTML map → ratified after mark" {
|
|
49
|
+
printf -- '<!doctype html>\n<head><title>map</title></head>\n<body><h1>map</h1></body>\n' > "$TMPD/m.html"
|
|
50
|
+
run bash "$SCRIPT" "$TMPD/m.html"; [ "$status" -eq 0 ]
|
|
51
|
+
grep -qE '<meta[^>]*name="human-oversight"[^>]*content="confirmed"' "$TMPD/m.html"
|
|
52
|
+
run is_story_map_ratified "$TMPD/m.html"; [ "$status" -eq 0 ]
|
|
53
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
#!/usr/bin/env bats
|
|
2
|
+
# Behavioural test for lib/story-oversight.sh — ADR-090 lazy-fingerprint helpers.
|
|
3
|
+
# The hash EXCLUDES the marker lines (so writing the marker is idempotent), and
|
|
4
|
+
# "ratified" = confirmed marker AND stored hash matches current content. Any
|
|
5
|
+
# content edit drifts the hash → not ratified (ADR-090 drift-invalidation).
|
|
6
|
+
#
|
|
7
|
+
# @adr ADR-090
|
|
8
|
+
# @problem P404 (Phase 2)
|
|
9
|
+
|
|
10
|
+
setup() {
|
|
11
|
+
REPO_ROOT="$(cd "$(dirname "$BATS_TEST_FILENAME")/../../../.." && pwd)"
|
|
12
|
+
LIB="${REPO_ROOT}/packages/itil/lib/story-oversight.sh"
|
|
13
|
+
TMPD="$(mktemp -d)"
|
|
14
|
+
# shellcheck source=/dev/null
|
|
15
|
+
source "$LIB"
|
|
16
|
+
}
|
|
17
|
+
teardown() { rm -rf "$TMPD"; }
|
|
18
|
+
|
|
19
|
+
@test "oversight_content_hash: identical content → identical hash" {
|
|
20
|
+
printf '# body\nline two\n' > "$TMPD/a"; printf '# body\nline two\n' > "$TMPD/b"
|
|
21
|
+
[ "$(oversight_content_hash "$TMPD/a")" = "$(oversight_content_hash "$TMPD/b")" ]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
@test "oversight_content_hash: adding/changing the marker lines does NOT change the hash" {
|
|
25
|
+
printf -- '---\nstatus: accepted\n---\n# body\n' > "$TMPD/f"
|
|
26
|
+
before="$(oversight_content_hash "$TMPD/f")"
|
|
27
|
+
printf -- '---\nstatus: accepted\nhuman-oversight: confirmed\noversight-hash: %064d\n---\n# body\n' 0 > "$TMPD/f"
|
|
28
|
+
[ "$before" = "$(oversight_content_hash "$TMPD/f")" ]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@test "oversight_content_hash: a real content edit DOES change the hash" {
|
|
32
|
+
printf '# body\n' > "$TMPD/f"; before="$(oversight_content_hash "$TMPD/f")"
|
|
33
|
+
printf '# body EDITED\n' > "$TMPD/f"
|
|
34
|
+
[ "$before" != "$(oversight_content_hash "$TMPD/f")" ]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@test "is_story_map_ratified: confirmed + matching hash → ratified (md)" {
|
|
38
|
+
printf -- '---\nstatus: accepted\n---\n# body\n' > "$TMPD/f"
|
|
39
|
+
h="$(oversight_content_hash "$TMPD/f")"
|
|
40
|
+
printf -- '---\nstatus: accepted\nhuman-oversight: confirmed\noversight-hash: %s\n---\n# body\n' "$h" > "$TMPD/f"
|
|
41
|
+
run is_story_map_ratified "$TMPD/f"; [ "$status" -eq 0 ]
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@test "is_story_map_ratified: confirmed but STALE hash (drifted) → not ratified" {
|
|
45
|
+
printf -- '---\nhuman-oversight: confirmed\noversight-hash: %064d\n---\n# body EDITED AFTER RATIFY\n' 1 > "$TMPD/f"
|
|
46
|
+
run is_story_map_ratified "$TMPD/f"; [ "$status" -ne 0 ]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@test "is_story_map_ratified: confirmed with NO hash (legacy hand-ratified) → not ratified" {
|
|
50
|
+
printf -- '---\nhuman-oversight: confirmed\n---\n# body\n' > "$TMPD/f"
|
|
51
|
+
run is_story_map_ratified "$TMPD/f"; [ "$status" -ne 0 ]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@test "is_story_map_ratified: unconfirmed → not ratified" {
|
|
55
|
+
printf -- '---\nhuman-oversight: unconfirmed\n---\n# body\n' > "$TMPD/f"
|
|
56
|
+
run is_story_map_ratified "$TMPD/f"; [ "$status" -ne 0 ]
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@test "is_story_map_ratified: HTML map confirmed + matching hash → ratified" {
|
|
60
|
+
printf '<h1>map</h1>\n' > "$TMPD/m.html"
|
|
61
|
+
h="$(oversight_content_hash "$TMPD/m.html")"
|
|
62
|
+
printf '<meta name="human-oversight" content="confirmed">\n<meta name="oversight-hash" content="%s">\n<h1>map</h1>\n' "$h" > "$TMPD/m.html"
|
|
63
|
+
run is_story_map_ratified "$TMPD/m.html"; [ "$status" -eq 0 ]
|
|
64
|
+
}
|