Skip to content

Commit a1a1d93

Browse files
authored
Merge pull request #285: S0 own-fix subscriptions candidates (A: extractor metadata, B: collector)
S0 — own-fix subscriptions candidates (analysis-only), Part A + Part B. The first increment of the subscription-autofix pipeline. Nothing edits source; it turns the extractor's `--fix-candidates` metadata into a deterministic, permission- tiered candidates.json for a later constrained apply. Part A — Roslyn extractor metadata (`--fix-candidates`, strictly additive; flag-off is byte-identical to pre-S0, `ownir_version` unchanged): a namespaced `fix` block per non-timer `+=` with a full TextSpan, SEMANTIC INotifyPropertyChanged classification, symbol-based `none`/`exact`/`ambiguous` teardown, conservative source/handler identity (a symbol is not a delegate identity), line-independent identity constituents, and a nested-type boundary; component `qualified_name` + shape; a committed pre-S0 golden gates flag-off byte-parity. Part B — the `own-fix subscriptions candidates` collector: exact-FQN selection with a safety envelope, versioned line-independent `finding_id`, config-pinned `target_api`, `allowed_actions` = convert_acquire (INotifyPropertyChanged only) | manual_review (convert_exact_teardown deferred to S2), deterministic ordering, per-file SHA-256, and hard failures for partial/nested/generated/unknown types, unknown finding-ids, bad schema/version, malformed shapes, and any source path escaping `--root`. Arbiter: S0 A+B ACCEPTED / CLOSED at e52ccbc.
2 parents ff21d4a + e52ccbc commit a1a1d93

11 files changed

Lines changed: 1998 additions & 21 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,6 +1608,74 @@ jobs:
16081608
fi
16091609
echo "OK: declared weak-subscribe wrapper = accepted release; += unaffected; own.toml plumbed; malformed config is a hard error"
16101610
1611+
# S0 (--fix-candidates, Part A): ADDITIVE fix-candidate metadata — a
1612+
# namespaced `fix` block on each `+=` subscription, component
1613+
# `qualified_name`/shape, and a top-level `fix_candidates_version`. With the
1614+
# flag OFF the facts are byte-identical (the extractor fact tests above run
1615+
# flag-off and would break otherwise); this step asserts the metadata is
1616+
# correct and that the off-run leaks none of it.
1617+
- name: S0 fix-candidates — extractor metadata (Part A)
1618+
run: |
1619+
fc=frontend/roslyn/samples/FixCandidatesSample.cs
1620+
dotnet run --project frontend/roslyn/OwnSharp.Extractor -- \
1621+
"$fc" --fix-candidates -o "$RUNNER_TEMP/fc_on.json"
1622+
dotnet run --project frontend/roslyn/OwnSharp.Extractor -- \
1623+
"$fc" -o "$RUNNER_TEMP/fc_off.json"
1624+
python tests/check_fix_candidates_facts.py "$RUNNER_TEMP/fc_on.json" "$RUNNER_TEMP/fc_off.json"
1625+
# REAL byte parity: the flag-off output must equal the committed pre-S0 golden
1626+
# (generated by the base extractor at the S0 branch point) BYTE-for-byte -- a
1627+
# true comparison, not "no new keys". Regenerate the golden (documented in
1628+
# tests/goldens/README.md) only when an unrelated extractor change intentionally
1629+
# alters this sample's facts.
1630+
if ! diff -u tests/goldens/fix_candidates_off.golden.json "$RUNNER_TEMP/fc_off.json"; then
1631+
echo "FAIL: flag-off facts drifted from the pre-S0 golden (byte parity broken)"; exit 1
1632+
fi
1633+
echo "OK: fix-candidate metadata correct; flag-off is byte-identical to the pre-S0 golden"
1634+
1635+
# S0 Part B: the `own-fix subscriptions candidates` collector turns the fix
1636+
# metadata into a deterministic candidates.json (analysis-only). Reuses fc_on.json.
1637+
- name: S0 fix-candidates — own-fix collector (Part B)
1638+
run: |
1639+
on="$RUNNER_TEMP/fc_on.json"
1640+
printf '[weak-subscription]\nsubscribe = ["WeakEvents.AddPropertyChanged"]\n' > "$RUNNER_TEMP/fix.toml"
1641+
# A leaky INotifyPropertyChanged subscription -> one candidate, convert_acquire allowed.
1642+
python -m ownlang own-fix subscriptions candidates "$on" \
1643+
--config "$RUNNER_TEMP/fix.toml" \
1644+
--class Own.Samples.FixCandidates.InpcNoTeardown \
1645+
--output "$RUNNER_TEMP/cand.json" --root .
1646+
python -c "
1647+
import json, sys
1648+
d = json.load(open(sys.argv[1]))
1649+
assert d['target_api'] == {'subscribe': 'WeakEvents.AddPropertyChanged'}, d['target_api']
1650+
assert len(d['candidates']) == 1, d['candidates']
1651+
c = d['candidates'][0]
1652+
assert c['allowed_actions'] == ['convert_acquire', 'manual_review'], c['allowed_actions']
1653+
assert c['event_contract'] == 'inotify_property_changed'
1654+
assert c['finding_id'].startswith('OWN001:sha256:'), c['finding_id']
1655+
assert d['source_files'][0]['sha256'].startswith('sha256:')
1656+
assert d['selection']['constraints']['max_types_changed'] == 1
1657+
print('OK: candidate bundle well-formed')
1658+
" "$RUNNER_TEMP/cand.json"
1659+
# A name_only event -> manual_review only.
1660+
python -m ownlang own-fix subscriptions candidates "$on" \
1661+
--config "$RUNNER_TEMP/fix.toml" \
1662+
--class Own.Samples.FixCandidates.NameOnlySubscriber \
1663+
--output "$RUNNER_TEMP/cand2.json" --root .
1664+
python -c "import json,sys; a=json.load(open(sys.argv[1]))['candidates'][0]['allowed_actions']; sys.exit(0 if a==['manual_review'] else 1)" "$RUNNER_TEMP/cand2.json" \
1665+
|| { echo "FAIL: name_only should be manual_review only"; exit 1; }
1666+
# A nested class -> hard error.
1667+
if python -m ownlang own-fix subscriptions candidates "$on" --config "$RUNNER_TEMP/fix.toml" \
1668+
--class Own.Samples.FixCandidates.OuterWithNested.Nested --output "$RUNNER_TEMP/x.json" --root . 2>/dev/null; then
1669+
echo "FAIL: a nested class must be refused"; exit 1
1670+
fi
1671+
# An unknown finding-id -> hard error.
1672+
if python -m ownlang own-fix subscriptions candidates "$on" --config "$RUNNER_TEMP/fix.toml" \
1673+
--class Own.Samples.FixCandidates.InpcNoTeardown --finding-id OWN001:sha256:deadbeef \
1674+
--output "$RUNNER_TEMP/x.json" --root . 2>/dev/null; then
1675+
echo "FAIL: an unknown finding-id must be refused"; exit 1
1676+
fi
1677+
echo "OK: own-fix collector — bundle, permission tiering, and hard rejections"
1678+
16111679
# The OwnTS frontend spike (P-020 Own.React): the SAME OwnIR seam, fed from a
16121680
# React .tsx instead of C#. Two analyses over the one core: (1) a useEffect
16131681
# acquire (timer / subscribe / listener) with no cleanup return is the core's

0 commit comments

Comments
 (0)