Skip to content

Commit 8ae3c36

Browse files
committed
docs: add checkpoint B5 documentation and refine test comments
Signed-off-by: Abhijeet Saharan <abhijeetsaharan2236@gmail.com>
1 parent e944958 commit 8ae3c36

2 files changed

Lines changed: 269 additions & 10 deletions

File tree

application/tests/cheatsheet_extractor_test.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,16 @@
3030

3131
EMPTY_MD = ""
3232

33-
# No ## headings — _extract_summary raises, _fallback_summary matches # via _ANY_HEADING_RE
34-
# and extracts body until len(markdown) since there is no next heading.
33+
# No ## Introduction heading exists, so summary extraction
34+
# falls back to the first available body content.
3535
BODY_UNDER_H1_MD = """\
3636
# Single Heading Cheat Sheet
3737
3838
Body text directly under H1, no subheadings at all.
3939
"""
4040

41-
# Leading spaces before # and ##malformed (no space) — both handled by \s* and (?!#) regex
41+
# Leading whitespace before H1 and malformed ## headings
42+
# should still be normalized and extracted correctly.
4243
MALFORMED_MD = """\
4344
# Malformed Title
4445
@@ -55,8 +56,8 @@ class TestNormal(unittest.TestCase):
5556
def setUp(self):
5657
self.record = extract_cheatsheet_record(NORMAL_MD, SOURCE_PATH)
5758

58-
# source, source_id, hyperlink, raw_markdown_path are derived from SOURCE_PATH
59-
# and are independent of markdown content — verified once here for all cases
59+
# source-derived fields should remain deterministic and
60+
# independent of markdown content across all extraction paths.
6061
def test_source(self):
6162
self.assertEqual(self.record.source, "owasp_cheatsheets")
6263

@@ -76,8 +77,8 @@ def test_summary(self):
7677
self.assertEqual(self.record.summary, "Storage guidance.")
7778

7879
def test_summary_bounded(self):
79-
# SUMMARY_MAX_LENGTH truncation happens in CheatsheetRecord.__post_init__
80-
# for every record — testing once here covers all cases
80+
# Summary truncation is enforced centrally via
81+
# CheatsheetRecord.__post_init__.
8182
self.assertLessEqual(len(self.record.summary), SUMMARY_MAX_LENGTH)
8283

8384
def test_headings(self):
@@ -114,7 +115,7 @@ def test_title_is_fallback(self):
114115
self.assertEqual(self.record.title, "No title found.")
115116

116117
def test_summary_no_summary_found(self):
117-
# No headings at all — _fallback_summary returns this literal string
118+
# Empty markdown should trigger terminal summary fallback.
118119
self.assertEqual(self.record.summary, "No summary found.")
119120

120121
def test_headings_empty(self):
@@ -132,11 +133,11 @@ def test_title(self):
132133
self.assertEqual(self.record.title, "Single Heading Cheat Sheet")
133134

134135
def test_summary_from_fallback_via_h1(self):
135-
# _fallback_summary matches # heading, extracts body until len(markdown)
136+
# Summary fallback should extract body content beneath the H1 section.
136137
self.assertIn("body text", self.record.summary.lower())
137138

138139
def test_headings_empty(self):
139-
# _HEADING_RE only matches ## — no ## present here
140+
# No valid ## headings should produce an empty headings list.
140141
self.assertEqual(self.record.headings, [])
141142

142143
def test_fallback_used(self):

docs/rfc-structured-extraction.md

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
# RFC Workstream B — Structured Extraction
2+
3+
This document explains the implementation and behavior of RFC Workstream B
4+
(Structured Extraction) from Cheatsheet to CRE Mapping RFC.
5+
6+
The goal of this module is to convert OWASP Cheat Sheet markdown into a
7+
deterministic structured object that downstream RFC workstreams can consume
8+
for categorization, retrieval, reranking, and mapping generation.
9+
10+
The implementation is primarily located in:
11+
12+
* `cheatsheet_defs.py`
13+
* `cheatsheet_extractor.py`
14+
15+
---
16+
17+
## Sources for more context
18+
19+
* RFC:
20+
`docs/rfc/cheatsheets-llm-autonomous-mapping-rfc.md`
21+
22+
* Checkpoints B1 & B2 implementation PR:
23+
`https://github.com/OWASP/OpenCRE/pull/912`
24+
25+
* Checkpoints B3 & B4 implementation PR:
26+
`https://github.com/OWASP/OpenCRE/pull/921`
27+
28+
---
29+
30+
## What Workstream B implements
31+
32+
**The implementation strictly follows the RFC extraction contract and prioritizes deterministic extraction behavior.**
33+
34+
It defines a typed dataclass named `CheatsheetRecord`.
35+
36+
This object represents the structured extraction result returned from:
37+
38+
```python
39+
extract_cheatsheet_record(markdown, source_path)
40+
```
41+
42+
The extractor parses OWASP Cheat Sheet markdown and returns normalized
43+
structured information about a cheatsheet.
44+
45+
`CheatsheetRecord` contains:
46+
47+
* `source`
48+
* `source_id`
49+
* `title`
50+
* `hyperlink`
51+
* `summary`
52+
* `headings`
53+
* `raw_markdown_path`
54+
* `category_hints`
55+
* `metadata`
56+
57+
---
58+
59+
## Fallback behavior
60+
61+
The extractor contains fallback functions capable of handling incomplete or
62+
malformed markdown containing:
63+
64+
* missing titles,
65+
* missing summary sources,
66+
* malformed headings.
67+
68+
These fallback paths ensure that extraction still returns a valid
69+
`CheatsheetRecord` object instead of failing entirely.
70+
71+
Fallback behavior is explicitly surfaced through:
72+
73+
```json
74+
"metadata": {
75+
"fallback_used": "true"
76+
}
77+
```
78+
79+
This allows downstream workstreams to identify records that required fallback
80+
logic during extraction and downstream normalization.
81+
82+
---
83+
84+
## Fallback decision tree
85+
86+
```text
87+
extract_cheatsheet_record(markdown, source_path)
88+
89+
90+
├── _extract_title(markdown)
91+
│ ├── H1 title exists
92+
│ │ → extract and normalize title
93+
│ │
94+
│ └── H1 title missing
95+
│ → _fallback_title()
96+
│ → "No title found."
97+
│ → metadata["fallback_used"] = "true"
98+
99+
└── _extract_summary(markdown)
100+
├── "Introduction" heading exists with body content
101+
│ → body beneath "Introduction" extracted as summary
102+
│ → summary normalized and truncated upto specifc length.
103+
104+
└── Introduction section missing or invalid
105+
→ _fallback_summary(markdown)
106+
107+
├── first heading with body content exists
108+
│ → its body returned as summary
109+
110+
└── no usable heading/body content exists
111+
→ "No summary found."
112+
→ metadata["fallback_used"] = "true"
113+
```
114+
115+
---
116+
117+
## Extraction examples
118+
119+
The following examples demonstrate deterministic extractor behavior across
120+
different markdown shapes.
121+
122+
Notes:
123+
124+
* Currently, `category_hints` is intentionally returned as an initial empty
125+
list during v1.
126+
127+
* `raw_markdown_path`, `hyperlink`, and `source_id` are derived from
128+
`source_path` (Module A) and are independent of markdown content.
129+
130+
---
131+
132+
## 1. Normal cheat sheet
133+
134+
### Example Input
135+
136+
```markdown
137+
# Secrets Management Cheat Sheet
138+
139+
## Introduction
140+
Storage guidance.
141+
142+
## Architectural Patterns
143+
Use vaults and environment isolation.
144+
```
145+
146+
### Output
147+
148+
```json
149+
{
150+
"source": "owasp_cheatsheets",
151+
"source_id": "Secrets_Management_Cheat_Sheet",
152+
"title": "Secrets Management Cheat Sheet",
153+
"hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Secrets_Management_Cheat_Sheet.html",
154+
"summary": "Storage guidance.",
155+
"headings": ["Introduction", "Architectural Patterns"],
156+
"raw_markdown_path": "cheatsheets/Secrets_Management_Cheat_Sheet.md",
157+
"category_hints": [],
158+
"metadata": {
159+
"parser_version": "v1",
160+
"fallback_used": "false"
161+
}
162+
}
163+
```
164+
165+
### Notes
166+
167+
* No fallback logic was required.
168+
169+
---
170+
171+
## 2. Missing H1 (fallback title)
172+
173+
### Input
174+
175+
```markdown
176+
## Introduction
177+
No H1 present.
178+
179+
## Details
180+
More content.
181+
```
182+
183+
### Output
184+
185+
```json
186+
{
187+
"source": "owasp_cheatsheets",
188+
"source_id": "Example_Cheat_Sheet",
189+
"title": "No title found.",
190+
"hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Example_Cheat_Sheet.html",
191+
"summary": "No H1 present.",
192+
"headings": ["Introduction", "Details"],
193+
"raw_markdown_path": "cheatsheets/Example_Cheat_Sheet.md",
194+
"category_hints": [],
195+
"metadata": {
196+
"parser_version": "v1",
197+
"fallback_used": "true"
198+
}
199+
}
200+
```
201+
202+
### Notes
203+
204+
* No H1 title exists, so the title defaults to `"No title found."`
205+
206+
---
207+
208+
## 3. Missing Introduction section (summary fallback)
209+
210+
### Input
211+
212+
```markdown
213+
# Single Heading Cheat Sheet
214+
215+
## Authentication
216+
217+
### Storage
218+
Secrets should be encrypted.
219+
```
220+
221+
### Output
222+
223+
```json
224+
{
225+
"source": "owasp_cheatsheets",
226+
"source_id": "Single_Heading_Cheat_Sheet",
227+
"title": "Single Heading Cheat Sheet",
228+
"hyperlink": "https://cheatsheetseries.owasp.org/cheatsheets/Single_Heading_Cheat_Sheet.html",
229+
"summary": "Secrets should be encrypted.",
230+
"headings": ["Authentication"],
231+
"raw_markdown_path": "cheatsheets/Single_Heading_Cheat_Sheet.md",
232+
"category_hints": [],
233+
"metadata": {
234+
"parser_version": "v1",
235+
"fallback_used": "true"
236+
}
237+
}
238+
```
239+
240+
### Notes
241+
242+
* No `Introduction` heading exists, so summary fallback logic is used.
243+
* The fallback scans all headings and returns the first non-empty body it
244+
finds — in this case the content beneath `### Storage`.
245+
* Only `##`-level headings appear in `headings``### Storage` is excluded.
246+
247+
---
248+
249+
## Additional behavior notes
250+
251+
It is ensured that markdown files with malformed title/headings such as:
252+
253+
* With leading whitespace (e.g. ` # My Title`, ` ## My Heading`)
254+
* No space after the marker (e.g. `##Authentication`)
255+
256+
are extracted correctly.
257+
258+
---

0 commit comments

Comments
 (0)