feat(pipeline): keycloak-session-store 25편·virtualization 59편을 S3→S5→S6 으로 돌린다

기록 84편을 계약 에이전트로 다시 썼다. 기존 71편(kss 25 · virt 46)과, 계약에만
있고 안 쓰여 있던 새 글감 13편이다. 원장 84개를 열어 단계마다 스킬 영수증과 관문
종료 코드를 적었고 verify-pipeline-run.py 가 error 0 으로 닫는다.

SSOT 결함 둘을 고쳤다.

- kss 의 `약 58일` 이 반입 중 `약 59일` 로 바뀌어 있었다. 원 증거 파일이
  「남은 일수: 88일 … 실제 갱신까지 약 58일」로 산수를 직접 적는다. D-4a 쪽
  `약 59일` 은 강제 갱신 뒤(`VALID: 89 days`)라 맞는 값이라 그대로 뒀다.
- virt §198 의 `11.6GB` 는 §178 의 원 측정 `Mem: 11648`(MiB)과 어긋나는데
  원 가이드의 표기 그대로라 고치지 않고 쓰이는 자리에 대조를 적었다.

기록의 수치 오류 셋을 고쳤다 — CASE 요약의 「게스트 셋에 8240MB」(5120+3120 은
둘이다), k3s 편이 같은 것을 여섯·일곱·여덟로 세던 것, no-docker 편의 「셋을 더
든다」(§281 의 표는 네 행이고 디스크 행이 빠져 있었다).

계약을 셋 고쳤다.

- kss 의 sourceRepository 리비전이 cdac9b8 이었는데 그 커밋에는 docs/guides/**
  28개가 아예 없다. 9465582b 로 바꾸고, 반입한 바이트가 어느 커밋과도 같지 않다는
  것을 측정값과 함께 적었다 — 반입은 커밋이 아니라 그 시점의 작업 트리에서 떠 온
  것이다(kss 297/306 · virt 12/14 가 작업 트리와 같고, 200 커밋을 거슬러 전수
  대조했을 때 가장 가까운 커밋도 28개가 어긋났다).
- virt 계약이 「2026-09-11 재배분」이라고 적는데 SSOT 는 재배분 날짜를 적지 않고
  재배분 뒤 값은 이미 2026-09-10 측정에 찍혀 있다.
- kss 후보 대장이 지나친 절 아홉에 처분을 적었다(warn 9 → 0). 새 글감은 0건이고
  넷은 앵커가 h3 슬러그의 접두가 아니라 중간 토막이라 검사기가 못 본 것이었다.

style_profile.mjs 의 결함 둘을 고쳤다 — frontmatter 가 문장으로 세어져
(실측 398자짜리 「문장」 하나) 평균 길이를 기준 안으로 밀어 올리고 있었고,
engPerSent 의 분자는 목록을 포함한 글에서, 분모는 목록을 걷어낸 글에서 세고
있었다(Question 기록에서 11.94 → 3.86).

verify-pipeline.py 전 항목 PASS · error 0 · unittest 334건 OK.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
DongHyeonka
2026-09-17 11:01:55 +09:00
co-authored by Claude Opus 5
parent d473609e0a
commit 2109f726fe
574 changed files with 159654 additions and 1551 deletions
@@ -0,0 +1,28 @@
# 같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다
## Alternative text
밖에서 온 packet 이 forward 훅에 붙은 base 체인 둘을 우선순위 순으로 지나는 경로도. 앞 체인의 accept 뒤에 libvirt guest_input 이 이어지고, 구멍을 넣기 전 경로는 그 체인 끝 reject 로, 넣은 뒤 경로는 체인 맨 앞의 구멍을 지나 엣지 nginx 로 갈라진다.
## Long description
왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝이 밖에서 친 curl 이 만든 inbound packet 이고 forward 훅으로 들어간다. 첫 점선 상자가 DNAT 파일에 둔 base 체인이다. priority filter - 10 이라 먼저 돌고, 그 안의 ct state new accept 가 이 packet 을 통과시킨다. 다음 점선 상자가 libvirt 가 만든 ip libvirt_network 테이블의 guest_input 체인이다. 앞 체인의 accept 는 평가를 끝내지 않으므로 같은 packet 이 이 체인으로 이어진다. 그 안의 상자 둘은 같은 체인의 맨 앞과 맨 끝이다. 구멍을 넣기 전에는 맨 앞이 비어 있어 packet 이 ct state established,related accept 에 걸리지 못한 채 체인 끝 reject 에 닿았고 connection refused 로 끝났다. 그 reject 규칙의 카운터가 4 패킷 240 바이트다. insert 로 구멍을 넣으면 그 규칙이 맨 앞에 서서 같은 packet 을 먼저 받아 192.168.122.10 의 엣지 nginx 로 보낸다. 실선이 구멍을 넣은 뒤의 경로이고 점선이 넣기 전의 경로다. 이 그림은 그 규칙이 libvirt 네트워크를 다시 세우면 사라진다는 것은 말하지 않는다.
## Elements and evidence
- **Boundary: forward · priority filter - 10** (system): DNAT 를 정의한 파일에 둔 base 체인. 같은 훅에서 libvirt 체인보다 먼저 돈다. Evidence: L7829L7830.
- **Boundary: ip libvirt_network · guest_input** (system): libvirt 가 자기 테이블 안에 만드는 base 체인. 같은 훅에서 뒤에 돈다. Evidence: L7818L7824.
- **inbound packet** (packet): 밖에서 친 curl 이 만든 packet. 호스트가 직접 듣는 리스너가 없어 FORWARD 로 간다. Evidence: L7814L7814, L7809L7809.
- **ct state new accept** (rule): 우리가 먼저 돌게 해 둔 체인의 규칙. 이 체인은 통과시키지만 평가를 끝내지는 않는다. Evidence: L7829L7833.
- **inserted accept** (rule): insert 로 guest_input 맨 앞에 넣은 구멍. add 로 넣으면 맨 뒤라 reject 뒤에 선다. Evidence: L7835L7840.
- **reject** (rule): guest_input 체인을 끝내는 규칙. 앞의 ct state established,related accept 에 걸리지 못한 packet 이 여기 닿는다. 이 카운터가 밖에서 친 curl 횟수와 일치해 범인 확정에 쓰였다. Evidence: L7821L7823, L7826L7827.
- **edge nginx** (service): 게스트에서 도는 엣지. 호스트에서 친 요청에는 이미 404 로 응답하고 있었다. Evidence: L7813L7813, L7839L7840.
- **connection refused** (result): drop 이 아니라 reject 라 기다리지 않고 즉시 돌아온 결과. Evidence: L7814L7814, L7816L7816.
## Relationships
- **inserted accept → edge nginx:** accept. Evidence: L7835L7840.
- **ct state new accept → inserted accept:** after insert. Evidence: L7831L7833, L7835L7836.
- **ct state new accept → reject:** before insert. Evidence: L7831L7833, L7821L7823.
- **inbound packet → ct state new accept:** forward hook. Evidence: L7814L7814, L7829L7830.
- **reject → connection refused:** reject. Evidence: L7823L7823, L7814L7816.
@@ -0,0 +1,30 @@
# 같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다
# Question: 밖에서 온 packet 하나가 forward 훅에서 어느 규칙을 어떤 순서로 지나 어디서 끝났고, insert 로 넣은 구멍은 그 순서의 어디에 들어가는가
direction: right
g0: "forward · priority filter - 10" {
n1: "ct state new accept" {
shape: rectangle
}
}
g1: "ip libvirt_network · guest_input" {
n2: "inserted accept" {
shape: rectangle
}
n3: "reject" {
shape: rectangle
}
}
n0: "inbound packet" {
shape: rectangle
}
n4: "edge nginx" {
shape: rectangle
}
n5: "connection refused" {
shape: rectangle
}
n0 -> g0.n1: "forward hook"
g0.n1 -> g1.n2: "after insert"
g0.n1 -> g1.n3: "before insert"
g1.n2 -> n4: "accept"
g1.n3 -> n5: "reject"
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="app.diagrams.net" modified="2026-07-23T00:00:00.000Z" agent="techviz-harness" version="24.7.17" type="device">
<diagram id="nftables-forward-hook-chain-order" name="같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다">
<mxGraphModel dx="1298" dy="434" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="1298" pageHeight="1169" math="0" shadow="0">
<root>
<mxCell id="0"/>
<mxCell id="1" parent="0"/>
<mxCell id="g_our-forward-chain" value="forward · priority filter - 10" style="swimlane;html=1;rounded=1;startSize=30;horizontal=1;dashed=1;strokeWidth=1.5;fillColor=#f7f9fb;strokeColor=#66788a;fontStyle=1;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="402.0" y="144.0" width="227.0" height="136.0" as="geometry"/>
</mxCell>
<mxCell id="g_libvirt-guest-input" value="ip libvirt_network · guest_input" style="swimlane;html=1;rounded=1;startSize=30;horizontal=1;dashed=1;strokeWidth=1.5;fillColor=#f7f9fb;strokeColor=#66788a;fontStyle=1;fontSize=13;" vertex="1" parent="1">
<mxGeometry x="729.0" y="35.0" width="234.0" height="354.0" as="geometry"/>
</mxCell>
<mxCell id="n_inbound-packet" value="inbound packet&lt;br/&gt;curl http://100.83.212.4" tooltip="밖에서 친 curl 이 만든 packet. 호스트가 직접 듣는 리스너가 없어 FORWARD 로 간다. | Evidence: L7814-L7814, L7809-L7809" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="70.0" y="186.5" width="202.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="n_our-accept" value="ct state new accept" tooltip="우리가 먼저 돌게 해 둔 체인의 규칙. 이 체인은 통과시키지만 평가를 끝내지는 않는다. | Evidence: L7829-L7833" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="432.0" y="190.0" width="167.0" height="64.0" as="geometry"/>
</mxCell>
<mxCell id="n_inserted-accept" value="inserted accept&lt;br/&gt;nft insert rule&lt;br/&gt;daddr 192.168.122.10&lt;br/&gt;tcp dport {80,443}" tooltip="insert 로 guest_input 맨 앞에 넣은 구멍. add 로 넣으면 맨 뒤라 reject 뒤에 선다. | Evidence: L7835-L7840" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="759.0" y="81.0" width="174.0" height="105.0" as="geometry"/>
</mxCell>
<mxCell id="n_tail-reject" value="reject&lt;br/&gt;chain tail&lt;br/&gt;counter packets 4&lt;br/&gt;bytes 240" tooltip="guest_input 체인을 끝내는 규칙. 앞의 ct state established,related accept 에 걸리지 못한 packet 이 여기 닿는다. 이 카운터가 밖에서 친 curl 횟수와 일치해 범인 확정에 쓰였다. | Evidence: L7821-L7823, L7826-L7827" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;strokeColor=#2563eb;strokeWidth=2;" vertex="1" parent="1">
<mxGeometry x="769.5" y="258.0" width="153.0" height="105.0" as="geometry"/>
</mxCell>
<mxCell id="n_edge-nginx" value="edge nginx&lt;br/&gt;192.168.122.10" tooltip="게스트에서 도는 엣지. 호스트에서 친 요청에는 이미 404 로 응답하고 있었다. | Evidence: L7813-L7813, L7839-L7840" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="1098.0" y="118.5" width="150.0" height="71.0" as="geometry"/>
</mxCell>
<mxCell id="n_connection-refused" value="connection refused" tooltip="drop 이 아니라 reject 라 기다리지 않고 즉시 돌아온 결과. | Evidence: L7814-L7814, L7816-L7816" style="whiteSpace=wrap;html=1;rounded=1;strokeWidth=2;fontSize=14;fontStyle=1;fillColor=#ffffff;strokeColor=#2d4357;verticalAlign=middle;" vertex="1" parent="1">
<mxGeometry x="1093.0" y="261.5" width="160.0" height="64.0" as="geometry"/>
</mxCell>
<mxCell id="e_inserted-to-edge" value="accept" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_inserted-accept" target="n_edge-nginx">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1039.5" y="143.8" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_our-chain-after-insert" value="after insert" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_our-accept" target="n_inserted-accept">
<mxGeometry relative="1" as="geometry">
<mxPoint x="703.0" y="173.2" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_our-chain-before-insert" value="before insert" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_our-accept" target="n_tail-reject">
<mxGeometry relative="1" as="geometry">
<mxPoint x="708.2" y="270.8" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_packet-to-our-chain" value="forward hook" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_inbound-packet" target="n_our-accept">
<mxGeometry relative="1" as="geometry">
<mxPoint x="352.0" y="194.0" as="offset"/>
</mxGeometry>
</mxCell>
<mxCell id="e_reject-to-refused" value="reject" style="edgeStyle=orthogonalEdgeStyle;rounded=0;orthogonalLoop=1;jettySize=auto;html=1;strokeWidth=2;endArrow=block;endFill=1;" edge="1" parent="1" source="n_tail-reject" target="n_connection-refused">
<mxGeometry relative="1" as="geometry">
<mxPoint x="1031.8" y="302.0" as="offset"/>
</mxGeometry>
</mxCell>
</root>
</mxGraphModel>
</diagram>
</mxfile>
@@ -0,0 +1,31 @@
{
"harness_version": "0.2.0",
"spec_id": "nftables-forward-hook-chain-order",
"spec_version": "1.1",
"spec_sha256": "7c412e3a6278b75551ce56f35693dd236d0b3bf0962c2788a87703123f009f9f",
"source_context": {
"document": "docs/virtualization/final/document.md",
"document_sha256": "60d902c7aed218ba637b48603a3bd0e6b193fea59c9de97ffdb8e05d5087aedd",
"anchor": {
"kind": "heading",
"value": "180. nftables 는 앞 체인의 `accept` 로 뒤 체인의 `reject` 를 막지 못한다",
"line": 7805
}
},
"outputs": [
"nftables-forward-hook-chain-order.svg",
"nftables-forward-hook-chain-order.drawio",
"nftables-forward-hook-chain-order.mmd",
"nftables-forward-hook-chain-order.d2",
"nftables-forward-hook-chain-order.excalidraw",
"nftables-forward-hook-chain-order.alt.md"
],
"lint_issue_count": 0,
"assumption_count": 0,
"assumptions_allowed": false,
"composition_profile": "component-flow",
"reference_ids": [
"payment-event-flow"
],
"diagram_only": true
}
@@ -0,0 +1,18 @@
%% 같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다
%% question: 밖에서 온 packet 하나가 forward 훅에서 어느 규칙을 어떤 순서로 지나 어디서 끝났고, insert 로 넣은 구멍은 그 순서의 어디에 들어가는가
flowchart LR
subgraph g_our_forward_chain["forward · priority filter - 10"]
n1["ct state new accept"]
end
subgraph g_libvirt_guest_input["ip libvirt_network · guest_input"]
n2["inserted accept"]
n3["reject"]
end
n0["inbound packet"]
n4["edge nginx"]
n5["connection refused"]
n0 -->|"forward hook"| n1
n1 -->|"after insert"| n2
n1 -->|"before insert"| n3
n2 -->|"accept"| n4
n3 -->|"reject"| n5
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="1298" height="434" viewBox="0 0 1298 434" role="img" aria-labelledby="diagram-title diagram-description">
<title id="diagram-title">같은 forward 훅에 붙은 base 체인 둘이 우선순위 순으로 이어서 평가되고, 앞 체인의 accept 는 뒤 체인의 reject 를 막지 못한다</title>
<desc id="diagram-description">왼쪽에서 오른쪽으로 읽는다. 왼쪽 끝이 밖에서 친 curl 이 만든 inbound packet 이고 forward 훅으로 들어간다. 첫 점선 상자가 DNAT 파일에 둔 base 체인이다. priority filter - 10 이라 먼저 돌고, 그 안의 ct state new accept 가 이 packet 을 통과시킨다. 다음 점선 상자가 libvirt 가 만든 ip libvirt_network 테이블의 guest_input 체인이다. 앞 체인의 accept 는 평가를 끝내지 않으므로 같은 packet 이 이 체인으로 이어진다. 그 안의 상자 둘은 같은 체인의 맨 앞과 맨 끝이다. 구멍을 넣기 전에는 맨 앞이 비어 있어 packet 이 ct state established,related accept 에 걸리지 못한 채 체인 끝 reject 에 닿았고 connection refused 로 끝났다. 그 reject 규칙의 카운터가 4 패킷 240 바이트다. insert 로 구멍을 넣으면 그 규칙이 맨 앞에 서서 같은 packet 을 먼저 받아 192.168.122.10 의 엣지 nginx 로 보낸다. 실선이 구멍을 넣은 뒤의 경로이고 점선이 넣기 전의 경로다. 이 그림은 그 규칙이 libvirt 네트워크를 다시 세우면 사라진다는 것은 말하지 않는다.</desc>
<metadata>{&quot;techviz&quot;:{&quot;spec_version&quot;:&quot;1.1&quot;,&quot;id&quot;:&quot;nftables-forward-hook-chain-order&quot;,&quot;profile&quot;:&quot;component-flow&quot;},&quot;source_context&quot;:{&quot;document&quot;:&quot;docs/virtualization/final/document.md&quot;,&quot;document_sha256&quot;:&quot;60d902c7aed218ba637b48603a3bd0e6b193fea59c9de97ffdb8e05d5087aedd&quot;,&quot;anchor&quot;:{&quot;kind&quot;:&quot;heading&quot;,&quot;value&quot;:&quot;180. nftables 는 앞 체인의 `accept` 로 뒤 체인의 `reject` 를 막지 못한다&quot;,&quot;line&quot;:7805}},&quot;evidence_policy&quot;:&quot;Each factual element cites source lines or is marked assumption.&quot;,&quot;diagram_only&quot;:true}</metadata>
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" />
</marker>
<style>
:root { color-scheme: light; }
text { font-family: Inter, Pretendard, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; fill: #111827; }
.canvas { fill: #ffffff; }
.group-box { fill: #ffffff; stroke: #9ca3af; stroke-width: 1.4; stroke-dasharray: 7 5; }
.group-label-bg { fill: #ffffff; }
.group-label { font-size: 13px; font-weight: 650; fill: #374151; }
.edge { fill: none; stroke: #374151; stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; marker-end: url(#arrow); }
.edge.style-dashed, .edge.semantic-dashed, .edge.assumption { stroke-dasharray: 7 5; }
.edge.style-dotted { stroke-dasharray: 2 5; }
.edge.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
.edge.emphasis-muted { stroke: #9ca3af; }
.edge.emphasis-warning, .edge.kind-failure, .edge.kind-error { stroke: #dc2626; stroke-width: 2.2; }
.edge-label-bg { fill: #ffffff; }
.edge-label { font-size: 12px; font-weight: 560; text-anchor: middle; }
.node-shape { fill: #ffffff; stroke: #4b5563; stroke-width: 1.7; }
.node-shape.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
.node-shape.emphasis-muted { stroke: #9ca3af; fill: #f9fafb; }
.node-shape.emphasis-warning { stroke: #d97706; stroke-width: 2; fill: #fffdf5; }
.node-shape.kind-database, .node-shape.kind-datastore, .node-shape.kind-storage { fill: #f8fafc; }
.node-shape.kind-queue, .node-shape.kind-event, .node-shape.kind-topic { fill: #fafafa; }
.node-shape.assumption { stroke-dasharray: 4 4; }
.storage-bottom, .controller-divider { fill: none; stroke: #4b5563; stroke-width: 1.4; }
.controller-led { fill: #4b5563; }
.actor-symbol { fill: none; stroke: #4b5563; stroke-width: 1.8; stroke-linecap: round; }
.actor-symbol.emphasis-primary { stroke: #2563eb; stroke-width: 2.2; }
.node-label { font-size: 14px; font-weight: 650; text-anchor: middle; }
.node-role { font-size: 10px; letter-spacing: 0.04em; text-anchor: middle; fill: #6b7280; }
.node-detail-divider { stroke: #d1d5db; stroke-width: 1; }
.node-detail { font-size: 11px; fill: #374151; }
.assumption-badge { font-size: 9px; font-weight: 700; fill: #92400e; }
.failure-mark { stroke: #dc2626; stroke-width: 4; stroke-linecap: round; }
.lifeline { stroke: #9ca3af; stroke-width: 1.2; stroke-dasharray: 5 5; }
.timeline-axis { stroke: #374151; stroke-width: 1.8; marker-end: url(#arrow); }
.timeline-stem { stroke: #6b7280; stroke-width: 1.3; }
.timeline-marker { fill: #ffffff; stroke: #374151; stroke-width: 1.7; }
.timeline-marker.primary { fill: #2563eb; stroke: #2563eb; }
.timeline-marker.warning { fill: #dc2626; stroke: #dc2626; }
.timeline-label { font-size: 13px; font-weight: 650; text-anchor: middle; }
.timeline-detail { font-size: 11px; fill: #4b5563; text-anchor: middle; }
</style>
</defs>
<rect class="canvas" width="1298" height="434" />
<rect class="group-box" x="402.0" y="144.0" width="227.0" height="136.0" rx="8" />
<rect class="group-label-bg" x="416.0" y="134.0" width="232.0" height="22" />
<text class="group-label" x="426.0" y="149.0">forward · priority filter - 10</text>
<rect class="group-box" x="729.0" y="35.0" width="234.0" height="354.0" rx="8" />
<rect class="group-label-bg" x="743.0" y="25.0" width="246.0" height="22" />
<text class="group-label" x="753.0" y="40.0">ip libvirt_network · guest_input</text>
<polyline class="edge kind-result style-solid emphasis-normal" points="933.0,133.5 1015.5,133.5 1015.5,154.0 1098.0,154.0" data-evidence="7835-7840" />
<rect class="edge-label-bg" x="1010.4" y="129.8" width="58.2" height="22" rx="3" />
<text class="edge-label" x="1039.5" y="144.8">accept</text>
<polyline class="edge kind-data style-solid emphasis-normal" points="599.0,213.0 679.0,213.0 679.0,133.5 759.0,133.5" data-evidence="7831-7833,7835-7836" />
<rect class="edge-label-bg" x="653.8" y="159.2" width="98.4" height="22" rx="3" />
<text class="edge-label" x="703.0" y="174.2">after insert</text>
<polyline class="edge kind-data style-dashed emphasis-normal" points="599.0,231.0 684.2,231.0 684.2,310.5 769.5,310.5" data-evidence="7831-7833,7821-7823" />
<rect class="edge-label-bg" x="655.7" y="256.8" width="105.1" height="22" rx="3" />
<text class="edge-label" x="708.2" y="271.8">before insert</text>
<polyline class="edge kind-data style-solid emphasis-normal" points="272.0,222.0 352.0,222.0 352.0,222.0 432.0,222.0" data-evidence="7814-7814,7829-7830" />
<rect class="edge-label-bg" x="302.8" y="180.0" width="98.4" height="22" rx="3" />
<text class="edge-label" x="352.0" y="195.0">forward hook</text>
<polyline class="edge kind-result style-dashed emphasis-normal" points="922.5,310.5 1007.8,310.5 1007.8,293.5 1093.0,293.5" data-evidence="7823-7823,7814-7816" />
<rect class="edge-label-bg" x="1002.6" y="288.0" width="58.2" height="22" rx="3" />
<text class="edge-label" x="1031.8" y="303.0">reject</text>
<g id="node-inbound-packet">
<rect class="node-shape kind-packet emphasis-normal role-source" data-evidence="7814-7814,7809-7809" x="70.0" y="186.5" width="202.0" height="71.0" rx="7" />
<text class="node-label" x="171.0" y="213.5">inbound packet</text>
<line class="node-detail-divider" x1="84.0" y1="234.5" x2="258.0" y2="234.5" />
<text class="node-detail" x="86.0" y="251.5">curl http://100.83.212.4</text>
</g>
<g id="node-our-accept">
<rect class="node-shape kind-rule emphasis-normal role-service" data-evidence="7829-7833" x="432.0" y="190.0" width="167.0" height="64.0" rx="7" />
<text class="node-label" x="515.5" y="220.0">ct state new accept</text>
</g>
<g id="node-inserted-accept">
<rect class="node-shape kind-rule emphasis-primary role-service" data-evidence="7835-7840" x="759.0" y="81.0" width="174.0" height="105.0" rx="7" />
<text class="node-label" x="846.0" y="108.0">inserted accept</text>
<line class="node-detail-divider" x1="773.0" y1="129.0" x2="919.0" y2="129.0" />
<text class="node-detail" x="775.0" y="146.0">nft insert rule</text>
<text class="node-detail" x="775.0" y="162.0">daddr 192.168.122.10</text>
<text class="node-detail" x="775.0" y="178.0">tcp dport {80,443}</text>
</g>
<g id="node-tail-reject">
<rect class="node-shape kind-rule emphasis-primary role-service" data-evidence="7821-7823,7826-7827" x="769.5" y="258.0" width="153.0" height="105.0" rx="7" />
<text class="node-label" x="846.0" y="285.0">reject</text>
<line class="node-detail-divider" x1="783.5" y1="306.0" x2="908.5" y2="306.0" />
<text class="node-detail" x="785.5" y="323.0">chain tail</text>
<text class="node-detail" x="785.5" y="339.0">counter packets 4</text>
<text class="node-detail" x="785.5" y="355.0">bytes 240</text>
</g>
<g id="node-edge-nginx">
<rect class="node-shape kind-service emphasis-normal role-sink" data-evidence="7813-7813,7839-7840" x="1098.0" y="118.5" width="150.0" height="71.0" rx="7" />
<text class="node-label" x="1173.0" y="145.5">edge nginx</text>
<line class="node-detail-divider" x1="1112.0" y1="166.5" x2="1234.0" y2="166.5" />
<text class="node-detail" x="1114.0" y="183.5">192.168.122.10</text>
</g>
<g id="node-connection-refused">
<rect class="node-shape kind-result emphasis-normal role-sink" data-evidence="7814-7814,7816-7816" x="1093.0" y="261.5" width="160.0" height="64.0" rx="7" />
<text class="node-label" x="1173.0" y="291.5">connection refused</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB