Commit 2aa4d34
authored
feat(tencent): add Tencent Cloud provider (#165)
* feat(tencent): add Tencent Cloud provider
Adds a Tencent Cloud provider to the Clanker CLI alongside the existing
AWS/GCP/Azure/Cloudflare/Fly/Verda/Vercel/Railway providers. Built up
in 15 phases over the past month against the bgdnvk/clanker upstream;
this PR consolidates the full provider as one contribution.
Coverage
* Compute cvm + lighthouse (lightweight cloud server)
* Network vpc, subnet, security-group + rule audit, eip, clb,
nat, vpn, ccn, direct-connect
* Storage cbs (Cloud Block Storage), cos (Object Storage)
* Database mysql (cdb), postgres, redis, mongodb, cynosdb (tdsql-c)
* Container tke clusters + kubeconfig fetch
* Edge cdn, edgeone, waf, anti-ddos
* Identity cam users
* Observability cloud monitor metrics (CVM + Lighthouse), cls log
topics, cloud audit tracks, alarm policies
* Cost monthly billing by product + top-N resources
* Tags flat map[string]string surfaced on all summary
structs that the SDK returns tags for (CVM, Lighthouse,
VPC, Postgres); reflection-based helper handles the
SDK's inconsistent tag-field naming across services.
Security audits
* public-cvm-exposure CVMs with sensitive ports open to 0.0.0.0/0
* clb-exposure public CLBs with risky listeners
* db-exposure managed DBs reachable from the public internet
* idle-eips EIPs unbound but billed
* unencrypted-cbs CBS volumes without encryption
* cert-expiry SSL certs expiring within N days
* cam-hygiene CAM users missing phone/email
* waf-coverage CDN/EdgeOne hosts not covered by WAF
* antiddos-coverage account anti-DDoS posture + per-region targets
* audit-coverage Cloud Audit tracks status
HTTP API
Hooks into the existing `clanker server` route table with bearer-auth
endpoints for inventory (/api/v1/tencent/resources/{type}), scans
(/api/v1/tencent/scan/{kind}), monitoring (/api/v1/tencent/metrics/
{cvm|lighthouse}), cost (/api/v1/tencent/cost/by-product, /resources),
and topology (/api/v1/tencent/topology). Maker plan and apply share
the existing /api/v1/maker/{plan,apply} endpoints.
Maker integration
* tencent-api verb 5-arg form [tencent-api, service, action,
region, json-params] dispatches to a generic
SendRaw over Tencent's CommonRequest signed
transport. No tccli dependency.
* tencent_prompts.go planner system prompt with chain shapes A-H,
anti-patterns, static-spec vs runtime
metrics rules, and the bgdnvk-style filter
verb example.
* filter verb new [filter, sourceIdx, arrayPath, field,
op, value] post-processor returns the
matching subset of a prior command's output.
Operators: > < >= <= == != contains
startsWith matches. Lets Maker answer
"find X by criteria" queries directly
instead of dumping full inventory.
* [*] array placeholders jsonPathString in internal/maker/exec.go
now handles $.X[*].Y wildcard paths,
binding to a JSON array literal so
"InstanceIds":<CVM_IDS> chains work.
* Action denylist knownHallucinatedActions catches common
LLM-invented Tencent action names with
"did you mean..." hints before the round-
trip (GetProductMetricData -> GetMonitorData,
cvm.ListInstances -> DescribeInstances, etc).
Credentials
Reads in this order: viper tencent.{secret_id,secret_key,region},
then TENCENTCLOUD_SECRET_* (official Tencent SDK env names),
then TENCENT_SECRET_* short aliases. Default region ap-singapore.
Dependencies
* tencentcloud/tencentcloud-sdk-go/{cvm,vpc,cbs,clb,cdb,postgres,
redis,mongodb,tke,tag,cam,monitor,cls,billing,lighthouse,...}
The provider is fully read-by-default; write operations
(Create/Modify/Run) go through Maker's existing plan+apply gate, with
destructive Terminate/Delete/Reset/Release/Discontinue actions
additionally requiring the existing --destroyer flag.
* fix(tencent): Lighthouse metric dimension must be PascalCase InstanceId
Tencent's two monitor APIs disagree about the canonical dimension casing
for namespace QCE/LIGHTHOUSE:
- DescribeBaseMetrics (metadata) reports "instanceid" (lowercase)
- GetMonitorData (data) accepts "InstanceId" (PascalCase)
We trusted DescribeBaseMetrics when building lighthouse.go, which is why
every Lighthouse metric call has been returning Tencent's misleading
"[InvalidParameterValue] : unauthorized operation or the instance has
been destroyed" — same error code Tencent reuses for genuine permission
gaps and lifecycle issues, which sent the debug down two days of false
leads (CAM, agent install, account type, sub-user vs root, ...).
The user's CAM is genuinely AdministratorAccess. The Cloud Monitor
agent is installed. The Tencent Console displays the metrics fine.
The data was always there — Tencent's data API just rejected our
spelling of the dimension name with a wildly inappropriate error code.
PascalCase is the same form CVM uses, so the fix is a one-character
change to the lighthouseDimensionKey constant. Added a comment block
explaining the discrepancy so the next person who reads this code
doesn't repeat the investigation.
Verified live against lhins-fprj6w5h (ap-singapore):
CpuUsage 0.80% (avg 0.96%, 59 samples)
MemUsage 38.79% (avg 38.72%, 59 samples)
DiskUsage 22.18% (60 samples)
LighthouseOutpkg 1 (avg 1.22, 59 samples)
* fix(tencent): cost API reports tax via DescribeCostExplorerSummary
DescribeBillSummaryByProduct (the per-product cost call) returns
RealCost but no tax field, so Clanker cost totals never matched the
Tencent console tax-inclusive headline. RealCost is total consumption
(voucher + cash + tax); the console headline is cash out of pocket.
Adds billFeeSummary() — calls DescribeCostExplorerSummary with
Dimensions=feeType, FeeType=cost, the only billing API that breaks out
tax. BillByProductJSON now embeds a summary object:
consumption total RealCost (voucher + cash + tax)
voucher amount covered by vouchers
cash_before_tax cash portion, pre-tax
tax tax amount
cash_incl_tax cash_before_tax + tax (matches console headline)
The Detail item names are localized display strings; the cash line is
"Total Amount After Discount (Excluding Tax)" which contains the word
"tax", so the substring match checks "discount" before "tax" to avoid
misclassifying the cash line as tax.
Verified against a real April 2026 bill:
consumption 11,146.37
voucher 5,701.68
cash_before_tax 4,905.13
tax 539.56
cash_incl_tax 5,444.70 = console "Total Cost (Incl Tax)"
* feat(tencent): voucher inventory + per-owner voucher deduction
- DescribeVoucherInfo / DescribeVoucherUsageDetails: voucher inventory,
balances, per-voucher usage history, and a per-owner-UIN breakdown of
voucher spend (nominal - balance). Voucher APIs only answer on the
account's home region, so they use a region-aware billing client.
- VoucherByOwnerJSON: month-scoped voucher deduction grouped by the
owner account UIN of each billed resource, via DescribeBillResourceSummary
(the voucher APIs carry no per-record UIN).
- CLI: `clanker tencent cost vouchers` / `cost voucher-usage`.
- HTTP API: /cost/vouchers, /cost/voucher-usage/{id}, /cost/voucher-by-owner.
* feat(tencent): show billing_mode + expires_at on list endpoints
Extend the slim JSON shape returned by /api/v1/tencent/resources/* for
every subscription-capable resource: CVM, Lighthouse, CBS, MySQL,
Postgres, Redis, MongoDB, CynosDB, CLB, AntiDDoS. PREPAID entries now
carry the renewal deadline so callers can see what is about to expire
without a separate billing call.
Tencent uses two billing-mode conventions (string vs int) and the int
form has NO consistent mapping across services — CDB inverts vs the
others. internal/tencent/charge_mode.go centralizes the normalization
into 'PREPAID' / 'POSTPAID' strings so the JSON shape is uniform.
* fix(tencent): generate maker SendRaw allowlist from vendored SDK
The maker plan executor (SendRaw) gated calls behind a hand-maintained
service map in raw.go, which silently lagged behind the SDK — calling
lighthouse.DescribeInstances failed with 'unsupported tencent service'
even though every other code path (typed clients, dashboard) handled
it fine. Eight other services (antiddos, billing, cdn, cloudaudit,
cynosdb, dc, ssl, teo, waf) were also missing from the map.
Replace the static map with a go:generate-driven one. gen_services.go
walks GOMODCACHE/.../tencentcloud-sdk-go/tencentcloud/*/v* and emits
service_versions_gen.go with one entry per service (latest version
when multiple are vendored). The cos sentinel is preserved via a
manualOverrides map in the generator. Upgrading the SDK now just needs
'go generate ./internal/tencent/...'.
The error message in SendRaw now enumerates services from the generated
map so it stays accurate.
* feat(tencent): show auto_renew on list endpoints
Add auto_renew (*bool, omitempty) to the slim JSON returned by
/api/v1/tencent/resources/* for every prepaid-capable resource where
the SDK exposes the renew flag: CVM, Lighthouse, CBS, MySQL, Postgres,
Redis, CynosDB, and CLB. Consumers can now check whether an expiring
resource will auto-renew or needs manual action — without the field,
expiring auto-renewers would generate false-positive alerts.
Skipped MongoDB and AntiDDoS — their list endpoints don't expose the
renew flag (would require a separate DescribeAutoRenew-style call).
Tencent's renew encoding has three flavors handled by new normalizers
in charge_mode.go: string 'NOTIFY_AND_AUTO_RENEW' (CVM/CBS/Lighthouse)
and 'AUTO_RENEW' (CLB nested in PrepaidAttributes), int64 1 (CDB/Redis/
CynosDB), uint64 1 (Postgres). Using *bool + omitempty so consumers can
distinguish 'not on auto-renew' from 'no info available'.
* fix(api,maker): address upstream PR #165 security review
Three critical findings from rafeegnash's review.
1. Constant-time token comparison (api/middleware.go)
The previous '!=' compare leaked information through response timing,
letting an attacker recover the bearer token byte-by-byte by measuring
latency across many requests. Switch to crypto/subtle.ConstantTimeCompare.
2. Refuse to start without a token (api/server.go, cmd/server.go)
An empty --token previously disabled auth on every route, including
POST /api/v1/maker/apply which can mutate real cloud resources. The
server now aborts startup unless --insecure is explicit (or
CLANKER_API_TOKEN is set). Help text now shows the token-gated
invocation as the default example.
3. Bound the filter 'matches' operator (maker/exec_tencent_filter.go)
regexp.Compile took an unbounded user-controlled pattern via the
maker plan. Go's RE2 is linear so a true ReDoS is not realistic, but
we now cap pattern length at 256 chars and run MatchString under a
100ms wall-clock deadline as defense-in-depth — the filter value
originates from LLM output and reaches us through the HTTP API, so
tighter bounds keep that surface predictable.
* fix(api,maker): address upstream PR #165 high-severity findings
Three high-severity items from rafeegnash's review.
1. Validate ?region= before it reaches the Tencent SDK (api/routes.go)
Previously any string was passed verbatim into the client's region —
enabling enumeration of arbitrary Tencent endpoints and potentially
driving unintended API charges. Added a regex covering all current
region prefixes (ap|na|eu|sa|cn)-name(-suffix)?, with a typed
*errInvalidRegion so the handler chokepoint surfaces 400 instead of
the catch-all 401. 23 handler call sites switched to a small
writeTencentClientErr helper.
2. CORS default no longer wildcard (api/server.go, cmd/server.go)
Default Access-Control-Allow-Origin was "*", letting any page read
API responses. Bearer auth via header mitigates CSRF but a hostile
origin could still siphon data when a user pastes their token there.
Default is now http://localhost:4173 (the bundled dashboard); pass
--cors-origin explicitly for non-localhost deployments.
3. Switch destructive check to read-only allowlist (maker/exec_tencent.go)
isTencentDestructive previously prefix-matched only Terminate|Delete|
Destroy|Reset|Release|Discontinue. CAM mutations like AddUser,
CreateAccessKey, AttachUserPolicy slipped through without --destroyer.
Flipped to an allowlist of read-only verb prefixes (Describe, Get,
List, Query, Lookup, Search, Check, Inquiry). Anything else now
requires --destroyer — fail-safe by default.
Behavior change to be aware of: verbs that don't match a read prefix
(Create*, Run*, Add*, Modify*, Set*, Enable*, Bind*, Associate*,
Allocate*, etc.) now require --destroyer. ResetInstancesPassword was
previously whitelisted as 'only changes the password' — that
whitelist is gone because a password reset locks out anyone using
the previous credential, which is a security-affecting mutation.
* fix(tencent,maker): address upstream PR #165 should-fix tier
Three items from rafeegnash's review.
1. ctx propagation + per-request timeout (tencent/profile.go, all clients)
The Tencent SDK has no WithContext variants — caller ctx cancellation
cannot interrupt a request in flight. As a defense, every typed client
now flows through newClientProfile(endpoint), which sets
HttpProfile.ReqTimeout = 30s. Combined with a ctxDone() check between
pagination pages and between GetRelevantContext sections, Ctrl-C now
bounds the wall-clock cost of cancellation to the single in-flight
SDK call (was: indefinite).
All 22 profile.NewClientProfile() + Endpoint sites were converted to
the helper; the now-unused profile import was stripped from those files.
2. Paginate GetRelevantContext past 100 (tencent/context.go)
contextCVMs, contextVPCs, contextSecurityGroups now loop through pages
with offset/limit until TotalCount is exhausted, capped at
gatherMaxItems (1000) with a logGatherTruncated() warning when the cap
fires. These are the highest-cardinality types — production accounts
commonly cross 100 here. Other gather functions still single-call at
limit=100; bringing them up is mechanical follow-up.
3. Tests for filter validator and matcher (maker/exec_tencent_filter_test.go)
Table-driven coverage for validateFilterCommand (arg count, sourceIdx,
op enum) and filterMatch (every operator + every JSON value type).
Includes the ReDoS-defense cases added in the critical-tier commit:
oversize pattern returns false, malformed regex returns false, PCRE-
style catastrophic-backtrack patterns don't hang.
* fix(tencent,api): address upstream PR #165 nits
Four small items from the review.
1. Credentials redaction (tencent/client.go)
Added String() and MarshalJSON() on Credentials so %v / %+v / Println
and json.Marshal all render SecretKey as **** instead of leaking the
raw key. Direct field access (the SDK signature path) is unchanged.
2. Drop doubled gather call (api/routes.go)
handleTencentResources called GetRelevantContext (full multi-section
gather) and discarded the result, then called gatherTencentByType for
the requested type — doubling SDK calls per request. Removed the
GetRelevantContext call.
3. NewClientWithCredentials factory for parity (tencent/client.go)
Added BackendTencentCredentials struct and NewClientWithCredentials
constructor matching the shape AWS / GCP / Fly.io / etc. already use.
Not wired into the backend credential flow yet — kept for consistency
so the dispatch layer can treat Tencent the same as every other
provider.
4. Per-field size cap on paramsJSON (tencent/raw.go)
maxParamsJSONBytes (256 KiB total) + maxParamsFieldBytes (64 KiB per
string field, walked recursively into nested maps and slices). The
effective cap was the 1 MiB HTTP body limit, which is far larger than
any legitimate Tencent action payload — 64 KiB still fits user-data
scripts and policy documents while rejecting accidentally-pasted
dumps from an LLM plan.
* feat(tencent): expiry report — CLI + HTTP
Cron-facing alert that walks every PREPAID-capable resource (CVM,
Lighthouse, CBS, MySQL, Postgres, Redis, MongoDB, CynosDB, CLB,
AntiDDoS, and SSL with --include-ssl) across the requested regions and
returns items at or below a renewal threshold.
• CLI: clanker tencent expiry --regions=ap-x,ap-y --threshold=14
Exit 0 = nothing flagged, 1 = items in window, 2 = already
expired. Drop-in for crontab + MAILTO, GitHub Actions, etc.
• HTTP: GET /api/v1/tencent/expiry?regions=&threshold=&manual_only=&include_ssl=
Returns the full report with counts breakdown (total / flagged
/ expired / auto_renew). Region query param is validated through
the same regex SendRaw uses, so SSRF-shaped inputs are rejected.
manual_only defaults to true so the cron only surfaces items that won't
auto-renew (the actionable subset); auto-renewers are still counted in
counts.auto_renew for visibility.
* fix(api,maker): address upstream PR #165 round-2 review
Six items from rafeegnash's second-pass review. The first is a
real production-killer; the rest are correctness + hygiene.
1. writeTencentClientErr no longer infinite-recurses (api/routes.go)
The catch-all branch from the security commit called itself instead
of writeError — stack overflow on the very first credential-missing
request, taking the process with it. New regression test in
api/routes_test.go (httptest + direct helper invocation, including
the wrapped-error errors.As path) locks the fix in.
2. countDestructiveCommands uses the live classifier (api/routes_maker.go)
The plan audit count was still computed from the old prefix denylist,
so AddUser / CreateAccessKey / AttachUserPolicy gated by --destroyer
recorded as 0 destructive commands. Exported IsTencentDestructive from
the maker package; routes_maker now delegates to it so the audit
count never drifts away from what the executor's safety gate enforces.
3. 401s no longer silent (api/middleware.go)
authMiddleware wraps logMiddleware so a rejected request short-circuits
before any access log fires. Added s.log401() that records method +
path + remote_addr + reason on every failed bearer attempt, so prod
credential rotations or auth attacks show up in stderr.
4. routes_plan returns 422 on unparseable LLM output (api/routes_plan.go)
Was 200 + 'warning' field, which forced clients to inspect the body
to know it failed. 422 Unprocessable Entity is the right semantic —
we understood the request but the upstream result was unprocessable.
The raw cleaned text is still in data.plan so the dashboard editor
can offer hand-correction.
5. Comments brought in line with code (api/server.go, maker/exec_tencent.go)
Config.CORSOrigin doc said '"*" by default'; now says http://localhost:4173.
validateTencentCommand docstring described the old prefix denylist;
now describes the allowlist.
---------
Co-authored-by: rephapeng <rephapeng@users.noreply.github.com>1 parent d4d94f9 commit 2aa4d34
59 files changed
Lines changed: 11356 additions & 4 deletions
File tree
- cmd
- internal
- ai
- api
- maker
- tencent
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
| 26 | + | |
26 | 27 | | |
27 | 28 | | |
28 | 29 | | |
| |||
118 | 119 | | |
119 | 120 | | |
120 | 121 | | |
| 122 | + | |
121 | 123 | | |
122 | 124 | | |
123 | 125 | | |
| |||
384 | 386 | | |
385 | 387 | | |
386 | 388 | | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
387 | 404 | | |
388 | 405 | | |
389 | 406 | | |
| |||
521 | 538 | | |
522 | 539 | | |
523 | 540 | | |
| 541 | + | |
524 | 542 | | |
525 | 543 | | |
526 | 544 | | |
| |||
549 | 567 | | |
550 | 568 | | |
551 | 569 | | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
552 | 573 | | |
553 | | - | |
| 574 | + | |
554 | 575 | | |
555 | 576 | | |
556 | 577 | | |
| |||
580 | 601 | | |
581 | 602 | | |
582 | 603 | | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
583 | 607 | | |
584 | 608 | | |
585 | 609 | | |
| |||
634 | 658 | | |
635 | 659 | | |
636 | 660 | | |
| 661 | + | |
| 662 | + | |
637 | 663 | | |
638 | 664 | | |
639 | 665 | | |
| |||
696 | 722 | | |
697 | 723 | | |
698 | 724 | | |
699 | | - | |
| 725 | + | |
700 | 726 | | |
701 | 727 | | |
702 | 728 | | |
| |||
847 | 873 | | |
848 | 874 | | |
849 | 875 | | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
850 | 881 | | |
851 | 882 | | |
852 | 883 | | |
| |||
1425 | 1456 | | |
1426 | 1457 | | |
1427 | 1458 | | |
| 1459 | + | |
1428 | 1460 | | |
1429 | 1461 | | |
1430 | 1462 | | |
| |||
2268 | 2300 | | |
2269 | 2301 | | |
2270 | 2302 | | |
| 2303 | + | |
| 2304 | + | |
| 2305 | + | |
| 2306 | + | |
| 2307 | + | |
| 2308 | + | |
| 2309 | + | |
| 2310 | + | |
| 2311 | + | |
| 2312 | + | |
| 2313 | + | |
| 2314 | + | |
| 2315 | + | |
| 2316 | + | |
| 2317 | + | |
| 2318 | + | |
| 2319 | + | |
| 2320 | + | |
| 2321 | + | |
| 2322 | + | |
| 2323 | + | |
| 2324 | + | |
| 2325 | + | |
| 2326 | + | |
| 2327 | + | |
| 2328 | + | |
| 2329 | + | |
| 2330 | + | |
| 2331 | + | |
| 2332 | + | |
| 2333 | + | |
| 2334 | + | |
| 2335 | + | |
| 2336 | + | |
| 2337 | + | |
| 2338 | + | |
| 2339 | + | |
| 2340 | + | |
| 2341 | + | |
| 2342 | + | |
| 2343 | + | |
| 2344 | + | |
| 2345 | + | |
| 2346 | + | |
| 2347 | + | |
| 2348 | + | |
| 2349 | + | |
| 2350 | + | |
| 2351 | + | |
| 2352 | + | |
| 2353 | + | |
| 2354 | + | |
| 2355 | + | |
| 2356 | + | |
| 2357 | + | |
| 2358 | + | |
| 2359 | + | |
| 2360 | + | |
| 2361 | + | |
| 2362 | + | |
| 2363 | + | |
| 2364 | + | |
2271 | 2365 | | |
2272 | 2366 | | |
2273 | 2367 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
12 | 12 | | |
13 | 13 | | |
14 | 14 | | |
| 15 | + | |
15 | 16 | | |
16 | 17 | | |
17 | 18 | | |
| |||
136 | 137 | | |
137 | 138 | | |
138 | 139 | | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
139 | 145 | | |
140 | 146 | | |
141 | 147 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
31 | 31 | | |
32 | 32 | | |
33 | 33 | | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
34 | 58 | | |
35 | 59 | | |
36 | 60 | | |
| |||
67 | 91 | | |
68 | 92 | | |
69 | 93 | | |
| 94 | + | |
70 | 95 | | |
71 | 96 | | |
72 | 97 | | |
| |||
94 | 119 | | |
95 | 120 | | |
96 | 121 | | |
| 122 | + | |
97 | 123 | | |
98 | 124 | | |
99 | 125 | | |
| |||
0 commit comments